СтатьиПлатформа nanoCAD 22 → Ваш ход, товарищ .NET, или Опять «Реверси» под nanoCAD

Ваш ход, товарищ .NET, или Опять  Реверси  под nanoCAD

Ваш ход, товарищ .NET, или Опять «Реверси» под nanoCAD

Ключевым нововведением версии nanoCAD 3.5 стало открытое API. Как пример новых возможностей в статье приводится написанная на .NET игра «Реверси»: кросс-САПР-платформенное приложение, способное работать не только под nanoCAD.

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

Как известно, лучший способ что-то изучить — это сделать его. Когда-то я писал «Реверси» под nanoCAD на скрипте. Теперь решил написать на .NET. В результате получилось кросс-САПР-платфор-менное приложение, способное работать не только под nanoCAD.

Программировать под nanoCAD можно было и раньше. dows писал на скриптах кривые Серпинского, я писал «Реверси», есть и другие примеры. Все это, конечно, хорошо, но мало. Поэтому мой следующий ход — .NET.

Entry level

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

  • создаем проект: Visual C#, Class Library;
  • добавляем в References библиотеки .NET nanoCAD: hostdbmgd.dll, host-mgd.dll;
  • регистрируем в nanoCAD команду.

Метод, который будет регистрироваться в качестве команды, должен иметь модификатор public и быть помечен специальным атрибутом CommandMethod.

Например, HelloWorld выглядит так:

[CommandMethod("HelloWorld")]
public void HelloWorld ()
{
	Editor ed = Platform.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
	// Выводим в командную строку сообщение
	ed.WriteMessage("Добро пожаловать в управляемый код nanoCAD!");
}

И ВСЁ!

Подробнее не пишу: об этом можно прочитать в nanoCAD SDK. Где взять? В Клубе разработчиков nanoCAD, регистрация открыта.

Структура

Игру я разделил на несколько классов: класс игры, класс игровой доски, класс информационной панели, класс игровой фишки:

  • класс игры должен содержать алгоритмы проверки возможности сделать ход по определенным координатам, поиска хода компьютера, подсчета фишек игроков, решения о продолжении игры;
  • класс доски — отрисовывать доску, хранить ее содержание;
  • класс информационной панели — показывать результаты прохождения партии;
  • класс фишки — отрисовывать фишку, уметь менять ее цвет, хранить всю информацию, касающуюся конкретной игровой ячейки.

Каждый класс должен быть максимально самостоятельным.

Дальше мне нужно было научиться создавать объекты, менять их и общаться с пользователем.

Создание объектов. Матчасть

Прежде чем рисовать «Реверси», требовалось понять — что делать, за что браться. Для того чтобы создать объекты, нужно немного знать о структуре документа. В каждом документе есть база данных. В базе данных хранятся объекты, содержащиеся в чертеже, и их связи друг с другом. Здесь хранится всё: и линии с дугами, и пространство модели, и стили текстов, и многое другое. Добавляя новый объект в чертеж, нужно добавить его в базу данных. А где есть база данных, там есть и транзакции.

Транзакции нужны, чтобы защитить наш документ: если в результате выполнения кода случится сбой, объекты, добавленные этим кодом, не попадут в документ — транзакция будет отменена. Если все завершилось успешно — транзакция подтверждается и объекты будут добавлены.

Database db = Application.DocumentManager. MdiActive Document.Database;
TransactionManager tm = db.TransactionManager;
using (Transaction tr = tm.StartTransaction())
{
	tr.Commit();
}

Просто создать объект мало. Он останется никуда не присоединенным, висящим в воздухе. Объект нужно куда-то поместить. Обычно это модельное пространство. В скриптах было что-то похожее (сказал модельному пространству: «Сделай линию» — и она там появится). В .NET немного по-другому: созданный объект нужно добавить в модельное пространство и в транзакцию.

using (Transaction tr = tm.StartTransaction())
{
	BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead, false) as BlockTable;
	BlockTableRecord ms = tr.GetObject(bt[BlockTableRecord.ModelS pace], OpenMode.ForWrite, false) as BlockTableRecord;
	Line line = new Line();
	ObjectId lid = ms.AppendEntity(line); // добавляем в модельное пространство tr.
	AddNewlyCreatedDBObject(line, true); // и в транзакцию
	tr.Commit(); // сохраняем изменения
}

Создание объектов-2. Полный вперед

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

Клетки я делал из штриховок. Открыв в NCadSDK.chm описание объекта Hatch (документация входит в SDK, доступный членам Клуба разработчиков), я почерпнул нужные мне знания. Третий абзац сразу сообщил мне, что штриховка состоит из петель, а список методов объекта штриховки подсказал магическое слово AppendLoop (). «Вот то, что мне нужно!» — подумал я.

Итак, каждую клетку я строил из квадратной полилинии, которую закрашивала штриховка. Все штриховки вместе образовывали квадрат 8 на 8 клеток.

Дальше — по накатанной, всё как в прошлый раз: бордюры и фишки создаю из объектов 3Dmesh. Бордюр — это полигон две на две вершины. Вычисляю координаты вершин, создаю их, добавляю в сеть, сеть добавляю в модель.

using (Transaction tr = tm.StartTransaction())
{
	// создаем сеть PolygonMesh mesh = new PolygonMesh(); mesh.NSize = 2; mesh.MSize = 2;
	ms.AppendEntity(mesh); tr.AddNewlyCreatedDBObject(mesh, true);
	// создаем и добавляем вершины AddVertexToMesh(mesh, new Point3d(col*gridstep, 0,-linehight), tr);
	AddVertexToMesh(mesh, new Point3d(col*gridstep, 0, linehight), tr); AddVertexToMesh(mesh, new Point3d(col*gridstep,8*gridstep,-line-hight), tr);
	AddVertexToMesh(mesh, new Point3d(col*gridstep,8*gridstep,line hight), tr);
	tr.Commit();
}
// создаем вершину сети private void
AddVertexToMesh(PolygonMesh PolyMesh, Point3d Pt3d, Transaction Trans)
{
	PolygonMeshVertex PMeshVer = new PolygonMeshVertex(Pt3d); PolyMesh.AppendVertex(PMeshVer);
	Trans.AddNewlyCreatedDBObject(PMesh Ver, true);
}

Отлично. Клетки есть, разделители есть. Нарисовать фишку теперь тоже нетрудно. Формулы вычисления координат вершин шарика я взял из скриптовой версии игры. Правда, подправил их, чтобы объект больше походил на игровую фишку «Реверси».

Что у меня получилось в результате — смотрите на рисунке.

«Я полсотни третий, выхожу на квадрат»

Теперь нужно научиться реагировать на действия пользователя.

Тут снова понадобится вспомнить мат-часть. Кроме базы данных, есть еще несколько объектов, которые относятся не к самому документу, а к приложению в целом. Это, например, объект Application — коллекция всех документов, открытых в приложении DocumentCollection. И объект взаимодействия с пользователем — Editor. Есть и другие, но их я сейчас не касаюсь.

У объекта Editor есть ряд методов для взаимодействия с пользователем: запрос объектов, запрос строки, числа, области. Запрос объекта осуществляется методом GetEntity (PromptEntityOptions). Объект PromptEntityOptions — это необязательные параметры. Через этот объект задаются строка приглашения, ключевые слова (если они нужны), выставляются ограничения на выбор объектов. Подобный объект принимают все методы ввода. Принцип хода остался прежним: пользователь выбирает клетку, куда хочет пойти. Клетка — это объект «Штриховка». Поэтому указываю, что принимаем в качестве ввода только объекты штриховки, пустой выбор — запретить, обязательно должен быть объект. И пишем строку приглашения.

Editor ed = Application.DocumentManager.MdiActive Document.Editor;
ObjectId selectObj = ObjectId.Null;
PromptEntityOptions opts = new PromptEntityOptions("Ваш ход.Укажите ячейку");
opts.SetRejectMessage("\nТолько ячейка может быть выбрана");
opts.AddAllowedClass(typeof(Hatch), false);
PromptEntityResult pr = ed.GetEntity(opts);

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

Перекрашивание существующих фишек

Как уже сказано, все объекты живут внутри базы данных. Это значит, что для того чтобы прочитать или изменить свойства какого-либо объекта, этот объект нужно открыть. Открытие объектов происхо дит методом транзакции GetObject (). По завершении изменений транзакция подтверждается.

using (Transaction myT = db.TransactionManager.StartTransaction())
{
	// pieceld - это id перекрашиваемой фишки в БД
	// открываем объект pieceld для изменений - OpenMode.ForWrite
	PolygonMesh piece = myTGetObject(this.pieceId, OpenMode.ForWrite) as PolygonMesh;
	// присваиваем цвет в зависимости от того, чья фишка
	piece.Color = (player == ePlayer.Human) ? Constants.HumanColor : Constants.PcColor;
	// подтверждаем транзакцию myTCommit();
}

Вкусняшки

Для хранения игровой доски в памяти я сделал две структуры данных: массив и словарь. Массив хранит образ доски 8 на 8, а словарь соответствия: элемент клетки — ObjectId-штриховки. Обе структуры хранят ссылки на объекты игровой доски. При таком подходе можно не заботиться о синхронизации. Меняться будет только элемент Piece. А получить его всегда можно по ссылке. Не важно, из массива или из словаря.

Dictionary<ObjectId, Piece> GameDesc = new Dictionary<ObjectId, Piece>();
Piece[,] GameDesc_xy = new Piece[8, 8];

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

int GetCounterCount(ePlayer player)
{
	// подсчет фишек игрока player
	return gamedesk.GameDesc.Where(x => x.Value.Player == player).Count();
}

Компиляция и запуск игры

Исходники игры можно взять тут: ftp.nanoCAD.ru. Откройте проект в Visual Studio или SharpDeveloper и скомпилируйте. Пути проекта настроены с расчетом на то, что nanoCAD установлен в стандартную директорию.

Если исходники вам не нужны, а хочется просто посмотреть на «Реверси», можно скачать собранный нами модуль (ftp.nanoCAD.ru.

Для запуска игры загрузите сборку MgdReversi.dll в nanoCAD командой NETLOAD. Теперь можно запускать игру командой PLAY.

Что не успел сделать

Было бы интересно, играя в nanoCAD, остановиться на середине партии, сохранить игру в файл, открыть этот файл в AutoCAD и доиграть — ведь формат файла в обеих системах один и тот же.

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

А до тех пор можно играть в «Реверси» не останавливаясь, от начала и до конца игры, что под AutoCAD, что под nanoCAD — и там и там игра работает одинаково. Достаточно лишь пересобрать «Реверси» под AutoCAD — используя его SDK, ObjectARX, это несложно.