[Из песочницы] Танчики в консоли, статья первая: «От спора к написанию кода»
Пожалуй нужно начать с небольшой предыстории: сижу я как-то на паре и решили мы с одногруппником поспорить о возможности создания простейших танчиков в консоли (по типу дендивских), но для игры по сети.
Так как компьютерных сетей у нас ещё не было, мне пришлось самой учить всё с нуля. Прочитав, пожалуй, страниц 30 отборного текста и прослушав четыре лекции по этой теме, мне стало очень скучно и лениво слушать это дальше, и я наконец приступила к проекту.
Ну что, все готовы? Начинаем!
Эта статья будет короткой, но информативной (для новичков, как я).
На момент написания статьи я знала всего несколько языков и рассуждала о выборе каждого из них и насколько он подходит для разработки этих самых танчиков. Но опираясь на знания я решила распределить всё так:
C# — клиент (так как самый лёгкий в изучении язык)
Rust — сервер (так как самый безопасный и быстрый)
Php/html/css/javascript — сайт (который мы ВОЗМОЖНО будем делать)
Часть первая: постановка задачи
Главное что я должна была сделать, дабы доказать правоту — это простые танчики, но я решила сделать универсальный клиент. Как это? — это когда сервер одинаково оптимизирован как и для WinForm, так и для консоли (потому что я хочу хорошие танчики в винформ).
Так что наша задача звучит так: Необходимо разработать три приложения, первое — для WinForm (стандартное окошко виндовс), второе — консольное (эмулятор денди) и третье — сам сервер.
Часть вторая: идеи и огрехи…
Что необходимо делать хорошему приложению? — этот вопрос я задала при проектировании и в моей голове прозвучал ответ: «Быть быстрым».
Что это значит? — то, что нам придётся работать с несколькими потоками приёма/передачи данных. Снаряд не может ждать, пока отрисуется танчик, танк не может ждать пока рисуется стена, чтобы пошевелиться.
Мы все знаем как это обидно, когда у тебя падает фпс или не успевает что-то отрисоваться и тебя убивают. Таких ситуаций быть не должно!
Подумав, я решила распределить их именно так:
1-й поток (мэйн) — должен отправлять нажатую клавишу на сервер.
2-й поток должен принимать координаты танков.
3-й координаты стен.
4-й координаты снарядов.
Также желательно было создать чат для общения танкистов во время игры (вдруг в разных кабинетах будем), но это пока не реализовано.
Теперь немного конкретики с потоками, первый должен быть простым отправителем (то есть организовывать минимальны вычисления и отправлять их на сервер), остальные же должны вечно принимать и отрисовывать всё в нашем приложении. В виде кода всё будет примерно так:
static async void Tank_coordinate()
{
//Приём координат танков
await Task.Run(
() =>
{
});
}
static async void Coordinate_wall()
{
//Приём координат стен
await Task.Run(
() =>
{
});
}
static async void Shot()
{
//Приём координат снарядов
await Task.Run(
() =>
{
/* ДЛЯ СПРАВКИ: ТУТ МЫ ПИШЕМ СВОЙ КОД, КОТОРЫЙ БУДЕТ ВЫПОЛНЕН АСИНХРОННО */
});
}
static void To_key()
{
//Приём нажатой клавиши
}
После всего этого у меня возникли несколько вариантов организации данных, для их отправки на сервер, и тут в бой пошли лекции. Выбор стоял великий: или всё организовать в интовских/стринговских переменных и рисовать через них, или создать структуру для данных, объекты я не рассматривала т.к. не хотела возится с ссылками. Спустя часик гугляния на форумах я остановилась на втором, так как организация данных в виде структуры гораздо легче, да и писать документацию будет удобнее (совет: если есть группы данных, которые схожи по назначению — лучше будет объединить их в структуру, ибо так гораздо легче читать код и искать переменные). Наша новая задача звучит так: создать структуру в которой будут поля отвечающие за нажатую клавишу, координату игрока, угол поворота танка и (для консоли) — позицию последнего символа и желательно какой-то регулятор отрисовки. Для чего нам последнее? — чтобы не использовались координаты с которыми мы работаем (увеличиваем/уменьшаем/отправляем на сервер)
В чём же были ошибки? — спросите меня вы.
Первоначально я начала лезть в дебри http модели (хотелось сделать на http), но спустя количество времени n мне стало ясно что лучше сделать на tcp (и проблем поменьше и с растом возится легче будет).
Мы определились с потоками и с идеей, что же дальше?
А дальше, друзья, будет самое интересное.
Часть третья: создание структуры и метода Main (). Конец первого этапа разработки
Сразу кину код, чтобы нетерпеливые читатели сразу его скопипастили:
//структура наша будет приватной
//и через методы мы присваиваем ей значения
///
/// Координаты игрока(численные значения)
///
public struct player_coor
{
public static void new_player_coor (int x_, int y_, string dir_, ConsoleKey key_, int last_x_, int last_y_)
{
x = x_; y = y_; dir = dir_; key = key_; last_x = last_x_; last_y = last_y_;
}
static int x = 2;//стартовые координаты
static int y = 2;//
static string dir;//стартовое положене
static ConsoleKey key;//нажатая клавиша
static int last_x;//
static int last_y;//последний 'y' и 'x'
}
static void Main(string[] args)
{
}
Это самый простецкий код, но он делает огромную работу: он распоточивает наше будущее приложение.
Небольшое описание потоков: любой поток создаётся через пространство System.Threading. Создаём мы его так же, как и экземпляр класса, но в аргументе потока указываем void функцию.
После создания потока его можно запустить методом .Start () и отключить (вызвать исключение) методом .Abort (), но это есть синхронная модель (то есть едим и ножом и вилкой), но есть асинхронная (мы едим и пылесосим, а ноги наши при этом делают жим лёжа по +100500 подходов), которую мы и возьмём к использованию.
Вот мы написали свой первый «псевдокод» и основные положения/идеи нашего проекта. Первая стадия подошла к концу, а так же подошло к концу наше бездумство, далее мы будем разрабатывать функции и нам придётся попотеть.
Огромное спасибо:
lair, unsafePtr, vlreshet, domix32,
vadimturkov, myxo за идеи и правки статьи.
Жду ваших пожеланий и идей, да и прибудет с вами сила!