Как я делал MMO-игру 2 (Великий Рандом, Runnable, сериализация)

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

Играя в онлайн-игры, я часто читал в чате возмущения на рандом. Кто-то убил 100500 монстров, но не выбил ни одного нормального артефакта. Кого-то убили тремя критами подряд. Кто-то попадает по врагу только 1 раз из 10, хотя шанс должен быть 50%. Я сам, играя в одну из последних браузерок в стиле БК, заметил, что на втором ударе обычно критую, поэтому всегда перед вторым ударом пил эликсир, увеличивающий урон. Понятно, что так не должно быть, рандом должен быть достаточно рандомным, чтобы играть было интересно. Возник вопрос, какую же функцию генерации псевдослучайных чисел стоит использовать в своей игре? Широко известный линейный конгруэнтный генератор достаточно быстр, но его качество мне показалось недостаточно хорошим даже при идеальных параметрах. Генератор java.util.Random, как оказалось, тоже использует линейный конгруэнтный метод. В итоге, после небольшого гугления, я остановил свой выбор на Mersenne Twister. Этот генератор выдает хорошие последовательности чисел и при этом имеет период такой длинны, что я смело могу инициализировать его только 1 раз и этого определенно хватит до следующего тысячелетия.

Код, реализующий алгоритм Mersenne Twister на Java, очень прост:public static int[] iMTs;

public static int iMT;

(iMTs=new int[624])[0]=(int)System.currentTimeMillis();for (iMT=1; iMT<624; iMT++){ int i; iMTs[iMT]=(1812433253*((i=iMTs[iMT-1])^(i>>30))+iMT);

}

public static int Random(){ if (iMT==624) { for (int i=(iMT=0); i<624; i++) { int j; iMTs[i]=iMTs[(i+397)%624]^((j=(iMTs[i]&(-2147483648))|(iMTs[(i+1)%624]&0x7FFFFFFF))>>1); if ((j&1)!=0) iMTs[i]^=-1727483681; } } return iMTs[iMT++]&0x7FFFFFFF;

}

Строки [1..2] - это объявление переменных алгоритма. Строки [4..9] - инициализация. А [11..23] - функция, которая возвращает случайное число. Теперь, чтобы получить случайное число от 0 до 99, достаточно написать "Random()%100".

С рандомом разобрался, дальше идет следующая интересная проблемка. Сервер получает данные от игрока в момент поступления HTTP-запроса. Это может быть, к примеру, команда на перемещение по карте, выполнение удара по врагу, добыча какого-нибудь ресурса. Надо защититься от того, что кто будет передавать такие запросы чаще чем надо. Существует много способов защиты, я решил сделать таким образом: при поступлении запроса команда будет просто записываться в память, а обрабатываться она будет с определенной периодичностью в специальном потоке (thread), если кто-то будет подавать команды чаще чем надо, то каждая новая команда будет просто перезатирать старую. Реализовать поток в сервлете можно с помощью интерфейса Runnable, добавив к объявлению класса сервлета "implements Runnable" и реализовав метод "public void run()". Осталось только запустить поток при инициализации сервлета. Для этого в метод "init()" я добавил строку "(new Thread(this)).start();".

Ну, а на последок все-таки немного рутины. Загрузка и сохранение данных об игровом мире и игроках. Хранить данные я решил в файле, а записывать и считывать их из/в память можно с помощью сериализации.public static int iPlayersQty;public static String[] sLogins;

public static String[] sPasswords;

FileInputStream oFIS;ObjectInputStream oOIS;iPlayersQty=(oOIS=new ObjectInputStream(oFIS=new FileInputStream(getServletConfig().getServletContext().getRealPath("game.dat")))).readInt();sLogins=(String[])oOIS.readObject();sPasswords=(String[])oOIS.readObject();oOIS.close();

oFIS.close();

FileOutputStream oFOS;ObjectOutputStream oOOS=new ObjectOutputStream(oFOS=new FileOutputStream(getServletConfig().getServletContext().getRealPath("game.dat")));oOOS.writeInt(iPlayersQty);oOOS.writeObject(sLogins);oOOS.writeObject(sPasswords);oOOS.close();

oFOS.close();

Строки [1..3] - это объявление переменных для хранения данных в памяти. [5..11] - чтение данных из файла. [13..19] - запись в файл.

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

Комментарии

Cool! Рандом действительно должен быть не предсказуем.

Submitted by Militari on

А на Xorshift RNGs смотрел? Тоже вроде ничего если судить по описанию.

// 32-битная реализацияunsigned long xor(){ static unsigned long y=2463534242; yˆ=(y<<13); y^=(y>>17); return (yˆ=(y<<5));}

// 64-битная реализацияunsigned long long xor64(){ static unsigned long long x=88172645463325252LL; xˆ=(x<<13); xˆ=(x>>7); return (xˆ=(x<<17));}

Submitted by Victor on

Читал про него, но не вникал. Когда буду тюнинговать сервер по скорости, надо будет посмотреть даст ли использование Xorshift заметный прирост производительности.

GameDev.by