Через несколько дней начнется чемпионат Европы по футболу - второй по значимости турнир среди сборных. Как и в любом другом турнире в нем будут как интересные, так и достаточно проходные для нейтрального болельщика матчи вроде “Дания - Финляндия” или “Польша - Словакия”. Смотреть такие матчи не всегда интересно, поэтому мы придумали в офисе игру, благодаря которой всегда есть за кого болеть, что значительно добавляет интерес просмотру. Мы играли в нее во время каждого крупного футбольного турнира, и правила игры претерпели ряд изменений по сравнению с первоначальными, чтобы сделать ее более интересной.
Игра представляет собой тотализатор с элементами покера, в которую могут играть любое количество участников. Лучше всего в нее играть с коллегами, чтобы была возможность каждый день обсудить перепетии всех событий вчерашних матчей. В этой статье я опишу механику игры, и как быстро и легко организовать ее у себя в офисе среди коллег.
Механика
Суть игры заключается в том, что участники должны делать ставки на каждый матч, по результатам которого между участниками делятся очки пропорционально баллам. За угаданный счет дается 3 балла. За правильную разницу голов - 2 балла, а за угаданный результат - 1 балл.
Пример
За матч дается 100 очков, и он завершился победой хозяев со счетом 3:1. Один участник поставил на счет 3:1, еще 2 участника на счет 2:0 и 4:2, и еще 3 участника на счет 1:0, 4:1 и 5:1. В сумме они набрали 10 баллов (1 * 3 + 2 * 2 + 3 * 1). Таким образом первый участник получит 30 очков (100 / 10 * 3), а другие по 20 и 10 очков, соответственно.
Такая механика игры мотивируют участников не только угадывать результат игры, исходя из сил футбольных команд, а также угадывать на какой результат сделают ставки другие участники. Например, если в матче Венгрия - Германия 9 человек поставят на гостей, а только один на хозяев, то в случае победы Венгрии он сорвет джек-пот и получит все 100 очков, резко вырвавшись вперед. Правда на практике это иногда приводило к тому, что большинство людей ставили именно на аутсайдера, рассчитывая, что все сделают ставку на фаворита. Особенно сильно это проявлялось в конце турнира, когда участникам с небольшим количеством очков хотелось в конце вырваться вперед за счет “джек-потов”.
Также мы решили сделать, чтобы количество очков за матч возрастало к концу турнира, чтобы у аутсайдеров не терялся интерес. Так за групповой раунд мы начисляли 100 очков, за ? финала - 200, четвертьфинал - 300, полуфинал - 400, а финал стоил 500 очков. Благодаря такой схеме, самая “жара” начиналась именно к концу турнира, и иногда, даже с последнего места, за несколько последних матчей кто-то вырывался на первые места.
Одним из важных моментов для увлекательности игры является то, что участники видят ставки других участников сразу после начала матча (естественно, менять после начала матча свою ставку уже нельзя). В результате, в зависимости от текущего счета, выгодный для участника результат может меняться на совершенно другой в зависимости от текущей турнирной ситуации. Например, если кто-то поставил на победу крупную победу фаворита, и все остальные тоже, то в случае ничьи к концу игры выгоднее, чтобы игра завершилась победой аутсайдера.
Чтобы сделать игру более азартной можно организовать внутренний банк, куда все участники скинут какую-то сумму, а затем он будет поделен между победителями всего турнира. Можно, например, чтобы победитель забирал 50% всего банка, второе место - 30%, а третье - 20%. Количество призовых мест можно увеличить, если участников много, чтобы в конце турнира был интерес для тех, кто находится внизу таблицы.
Принципиальное отличие этой игры от классического тотализатора в том, что в современных букмекерских конторах игрок соревнуются против людей во всем мире, так как компании ставят друг у друга. Кроме того, статистически можно выиграть только, если человек лучше разбирается в футболе, чем средний игрок. В этой же игре люди играют против своих же коллег, и победа больше зависит не столько от знания футбола, сколько от понимания психологии своих противников. Плюс нет комиссии организатору :)
Организация турнира
Итак, что нужно, чтобы организовать у себя в офисе турнир ? Прежде всего нужна программа с веб-интерфейсом. Ее мы быстро сделали на открытой и бесплатной платформе lsFusion. Нельзя сказать, что там просто идеальный пользовательский интерфейс, но для организации игры нам было достаточно. Программу можно установить как на своем сервере (но нужно, чтобы она была доступна извне, так как матчи обычно проходят в нерабочее время), так и воспользоваться уже установленной программой на нашем выделенном сервере. Второй вариант не требует никаких дополнительных действий, поэтому я сначала расскажу именно про него.
Точка доступа в программу находится по адресу : https://euro.lsfusion.org. В ней можно одновременно проводить сколько угодно турниров. Заходить в систему можно как с настольного компьютера, так и с мобильного или планшета.
Для организации своего турнира нужен организатор. Он будет подключать к турниру других участников, а также, при необходимости, собирать и хранить банк. Организатор должен сначала зарегистрироваться по вышеуказанному адресу, нажав кнопку Зарегистрироваться :
Все поля обязательные для заполнения, но в них можно заполнять что угодно. Проверки email не проводится, но если его указать неправильно, то невозможно будет восстановить пароль. Логин должен быть глобально уникальный. В самом турнире будут показываться только логин, имя и фамилия. Поэтому желательно их заполнять таким образом, чтобы остальные участники могли определить кто есть кто (но не обязательно вводить реальные личные данные).
Таким же образом должны зарегистрироваться все остальные участники. После регистрации участника организатор может добавить его в свой турнир нажав кнопку Добавить игрока на форме Таблица :
На открывшейся форме нужно ввести логин участника, которого необходимо добавить в турнир.
В остальном ответственность организатора ничем не отличается от участников. При входе в программу открываются две закладки :
В этой форме пользователь должен в последних двух колонках (зеленых) верхней таблицы сделать ставку на счет для каждого матча. В нижней части можно посмотреть кто уже сделал ставку, а после начала матча - какую именно.
Здесь участник может посмотреть итоговую таблицу по набранным очкам в режиме реального времени.
Кроме того каждому участнику доступна форма Глобальная таблица, где рассчитывается итоговая таблица для всех участников, как будто они участвуют в одном турнире. В ней не показываются имена игроков, а только логины.
Опыт показывает, что лучше каждому участнику сделать ставки сразу на все матчи, так как их можно поменять в любой момент до начала матчи. Иначе, в случае форс-мажора, участник может забыть сделать хоть какую-то ставку, и не будет участвовать в розыгрыше очков по начавшемуся матчу.
Если по какой-то причине хочется провести турнир на своем сервере, то это можно сделать следующим образом :
Установка на собственный сервер
Для установки потребуется виртуальный сервер под Linux с 2ГБ памяти.
Сначала нужно установить платформу lsFusion, как указано в этой инструкции.
Затем создать в папке /var/lib/lsfusion файл с именем Euro2020.lsf и поместить туда следующий код :
MODULE Euro2020;
REQUIRE Authentication, Time;
CLASS Team 'Команда';
name 'Название' = DATA ISTRING[50] (Team) IN id;
FORM team 'Команда'
OBJECTS t = Team PANEL
PROPERTIES(t) name
EDIT Team OBJECT t
;
FORM teams 'Команды'
OBJECTS t = Team
PROPERTIES(t) READONLY name
PROPERTIES(t) NEWSESSION NEW, EDIT, DELETE
LIST Team OBJECT t
;
CLASS Match 'Матч';
dateTime 'Начало матча' = DATA DATETIME (Match) NONULL IN id;
started (Match m) = dateTime(m) < currentDateTime();
CONSTRAINT CHANGED(dateTime(Match m)) AND PREV(dateTime(m)) AND NOT DROPPED(m IS Match) MESSAGE 'Запрещено изменять время начала матча';
homeTeam 'Хозяева' = DATA Team (Match) NONULL;
nameHomeTeam 'Хозяева' (Match m) = name(homeTeam(m)) IN id;
awayTeam 'Гости' = DATA Team (Match) NONULL;
nameAwayTeam 'Гости' (Match m) = name(awayTeam(m)) IN id;
homeScore 'Результат (хозяева)' = DATA INTEGER (Match);
awayScore 'Результат (гости)' = DATA INTEGER (Match);
homeScore 'Ставка (хозяева)' = DATA INTEGER (Match, CustomUser);
awayScore 'Ставка (гости)' = DATA INTEGER (Match, CustomUser);
home 'Победа (хозяева)' (Match m) = (GROUP SUM 1 IF homeScore(m, CustomUser c) > awayScore(m, c)) IF started(m);
tie 'Ничья' (Match m) = (GROUP SUM 1 IF homeScore(m, CustomUser c) = awayScore(m, c)) IF started(m);
away 'Победа (гости)' (Match m) = (GROUP SUM 1 IF homeScore(m, CustomUser c) < awayScore(m, c)) IF started(m);
startedHomeScore 'Ставка (хозяева)' (Match m, CustomUser c) = homeScore(m, c) IF started(m);
startedAwayScore 'Ставка (гости)' (Match m, CustomUser c) = awayScore(m, c) IF started(m);
hasBet 'Сделана ставка' (Match m, CustomUser c) = homeScore(m, c) AND awayScore(m, c);
countBets 'Кол-во ставок' (Match m) = GROUP SUM 1 IF hasBet(m, CustomUser c);
CONSTRAINT DROPPED(Match m IS Match) AND PREV(countBets(m)) MESSAGE 'Запрещено удалять матч, по которому сделаны ставки';
CONSTRAINT CHANGED(homeScore(Match m, CustomUser c)) AND started(m) MESSAGE 'Запрещено изменять результат начавшегося матча';
CONSTRAINT CHANGED(awayScore(Match m, CustomUser c)) AND started(m) MESSAGE 'Запрещено изменять результат начавшегося матча';
CONSTRAINT CHANGED(homeScore(Match m)) AND NOT started(m) MESSAGE 'Запрещено изменять счет матча до начала матча';
CONSTRAINT CHANGED(awayScore(Match m)) AND NOT started(m) MESSAGE 'Запрещено изменять счет матча до начала матча';
currentHomeScore 'Ставка (хозяева)' (Match m) = homeScore(m, currentUser());
currentAwayScore 'Ставка (гости)' (Match m) = awayScore(m, currentUser());
changeCurrentHomeScore (Match m) {
FOR CustomUser u = currentUser() DO
INPUT =homeScore(m, u) CHANGE;
}
changeCurrentAwayScore (Match m) {
FOR CustomUser u = currentUser() DO
INPUT =awayScore(m, u) CHANGE;
}
// Расчет очков
cost 'Стоимость' = DATA INTEGER (Match);
correctScore (Match m, CustomUser c) = homeScore(m, c) == homeScore(m) AND awayScore(m, c) == awayScore(m);
correctScore 'Правильный счет' (CustomUser c) = GROUP SUM 1 IF correctScore (Match m, c);
correctDelta (Match m, CustomUser c) = homeScore(m, c) - awayScore(m, c) == homeScore(m) - awayScore(m);
correctDelta 'Правильная разница' (CustomUser c) = GROUP SUM 1 IF correctDelta (Match m, c);
correctResult (Match m, CustomUser c) = (homeScore(m, c) - awayScore(m, c)) * (homeScore(m) - awayScore(m)) > 0 OR
(homeScore(m, c) == awayScore(m, c) AND homeScore(m) == awayScore(m));
correctResult 'Правильный результат' (CustomUser c) = GROUP SUM 1 IF correctResult (Match m, c);
points 'Кол-во баллов' = CASE
WHEN correctScore(Match m, CustomUser c) THEN 3
WHEN correctDelta(m, c) THEN 2
WHEN correctResult(m, c) THEN 1;
points 'Кол-во баллов' (CustomUser c) = GROUP SUM points(Match m, c);
points 'Кол-во баллов' (Match m) = GROUP SUM points(m, CustomUser c);
pointsStarted 'Кол-во баллов' (Match m) = points(m) IF started(m);
score 'Кол-во очков' (Match m, CustomUser c) = NUMERIC[16,4](cost(m) * points(m, c) / points(m));
score 'Кол-во очков' (CustomUser c) = GROUP SUM score(Match m, c);
FORM match 'Матч'
OBJECTS m = Match PANEL
PROPERTIES(m) cost, dateTime, nameHomeTeam, nameAwayTeam, homeScore, awayScore
EDIT Match OBJECT m
;
DESIGN match {
GROUP(,m) {
columns = 1;
}
}
backgroundScore = RGB(255, 224, 224);
backgroundBet = RGB(224, 224, 255);
backgroundOwn = RGB(224, 255, 224);
FORM matches 'Ставки'
OBJECTS m = Match
PROPERTIES(m) READONLY cost, dateTime, nameHomeTeam, nameAwayTeam
PROPERTIES(m) BACKGROUND backgroundScore() READONLY homeScore, awayScore, pointsStarted
PROPERTIES(m) BACKGROUND backgroundBet() countBets, home, tie, away
PROPERTIES BACKGROUND backgroundOwn() currentHomeScore(m) ON CHANGE changeCurrentHomeScore(m),
currentAwayScore(m) ON CHANGE changeCurrentAwayScore(m)
PROPERTIES(m) NEWSESSION NEW, EDIT, DELETE
OBJECTS c = CustomUser GRID
PROPERTIES(c) READONLY name[Contact]
PROPERTIES(m, c) READONLY hasBet
PROPERTIES(m, c) SHOWIF started(m) READONLY BACKGROUND backgroundScore() points, score
PROPERTIES(m, c) SHOWIF started(m) READONLY BACKGROUND backgroundBet() homeScore, awayScore
;
FORM standings 'Таблица'
OBJECTS c = CustomUser GRID
PROPERTIES(c) READONLY name[Contact], correctScore, correctDelta, correctResult, points, score
PROPERTIES(c) NEWSESSION NEW, EDIT, DELETE
OBJECTS m = Match GRID
PROPERTIES(m) READONLY cost, dateTime, nameHomeTeam, nameAwayTeam
PROPERTIES(m) BACKGROUND backgroundScore() READONLY homeScore, awayScore, pointsStarted
PROPERTIES(m, c) READONLY BACKGROUND backgroundScore() points, score
PROPERTIES(m, c) READONLY BACKGROUND backgroundBet() startedHomeScore, startedAwayScore
ORDERS score(c) DESC
;
NAVIGATOR {
NEW matches;
NEW standings;
NEW teams;
}
Перезапустить серверную службу при помощи команд :
systemctl stop lsfusion4-server
systemctl start lsfusion4-server
Приложение будет доступно по адресу http://<ip>:8080
Впервые мы провели игру еще во время чемпионата Европы 2012 года. Многие из наших сотрудников в итоге болели за кого-то в абсолютно всех матчах турнира, хотя в обычной ситуации не стали бы смотреть и половины. В этот раз мы решили поделиться нашим опытом с другими людьми. Если кому-то будет интересно, то тоже включайтесь в игру. В случае возникновения каких-либо проблем или вопросов всегда можно зайти в slack канал, где мы поможем по любым вопросам, или написать на почту info [собака] lsfusion.org.
fugasio
А зачем 2 гигабайта памяти?
CrushBy Автор
Просто все программы, которые ставятся инсталлятором по умолчанию, настроены на такое количество памяти. Соответственно, чтобы не описывать как уменьшать выделяемую память, и написал про 2ГБ. В принципе там можно запустить и на меньшей памяти, если уменьшить в настройках PostgreSQL память в /var/lib/pgsql/13/data/postgresql.conf и в файлах в папках /etc/lsfusion4-server и /etc/lsfusion4-client.
Понятно, что такую простую логику можно написать с меньшим потреблением памяти и без 3х служб — это явный оверхэд. Но я на коленке для внутреннего потребления все писал за час (в статье есть собственно весь код на 100 строк).