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

В результате получилось кросс-САПР-платформенное приложение, способное работать не только под nanoCAD-ом. Как это делалось — смотрите под катом.
Программировать под nanoCAD можно было и раньше. На скриптах dows писал кривые Серпинского, я писал Реверси, были и другие примеры с нашего форума. Это все, конечно, хорошо, но мало. Поэтому, следующий мой ход – .NET.
Первое, что нужно было сделать – создать сборку, содержащую код, исполняемый в nanoCAD:
Метод, который будет регистрироваться в качестве команды, должен иметь модификатор public и быть помеченным специальным атрибутом CommandMethod.
Например, HelloWorld выглядит так:
И ВСЕ!
Не пишу об этом более подробно, так как об этом можно прочитать в nanoCAD SDK. Где взять? В Клубе разработчиков nanoCAD, регистрация открыта.
Игру я разделил на несколько классов: класс игры, класс игровой доски, класс информационной панели, класс игровой фишки:
Дальше мне нужно было научиться создавать объекты, менять их и общаться с пользователем.
Прежде чем рисовать реверси нужно было понять – что делать, за что браться.
Для того, чтобы создать объекты, нужно немного знать о структуре документа. В каждом документе есть база данных. В базе данных хранятся объекты, содержащиеся в чертеже, и их связи друг с другом. Все хранится в БД: и линии с дугами, и пространство модели, и стили текстов и многое другое. Добавляя новый объект в чертеж, нужно добавить его в базу данных. А где есть база данных, там есть и транзакции.
Транзакции нужны, чтобы защитить наш документ: если в результате выполнения кода был сбой – объекты, добавленные этим кодом, не попадут в документ – транзакция будет отменена. Если все завершится успешно — транзакция будет подтверждена и объекты будут добавлены.
Просто создать объект мало. Он останется никуда не присоединенным и висящим «в воздухе». Объект нужно куда-то поместить. Обычно это модельное пространство. В скриптах было что-то похожее — сказал модельному пространству «сделай линию» – она там появится. В .NET немного по другому — нужно добавить созданный объект в модельное пространство и в транзакцию.
Каждый объект, добавленный в базу данных, однозначно определяется по личному коду — ObjectId. Используя ObjectId можно открывать объекты для чтения или записи.
Отлично. Вооружившись знаниями о внутренней кухне документа, можно, наконец, начинать разрабатывать класс игровой доски. Нет доски – нет и партии. Поэтому первое, что я начал делать – рисовать клетки в пространстве документа.
Клетки я делал из штриховок. Открыв в NCadSDK.chm описание объекта Hatch (документация входит в SDK, доступный членам Клуба разработчиков), я почерпнул нужные мне знания. Третий абзац сразу сообщил мне, что штриховка состоит из петель, а список методов объекта штриховки подсказал магическое слово AppendLoop(). Вот то, что мне нужно, подумал я.
Итак, каждую клетку я строил из квадратной полилинии, которую закрашивала штриховка. Все штриховки вместе образовывали квадрат 8 на 8 клеток.
Дальше – по накатанной, все как в прошлый раз: бордюры и фишки создаю из объектов 3Dmesh. Бордюр это полигон 2 на 2 вершины. Вычисляю координаты вершин, создаю их, добавляю в сеть, сеть добавляю в модель.
Отлично. Клетки есть, разделители есть. Нарисовать фишку теперь тоже не трудно. Формулы вычисления координат вершин шарика я взял из скриптовой версии игры. Правда, я их подправил, чтобы объект больше походил на игровую фишку реверси.
Вот, что у меня получилось в результате:

Теперь нужно научиться реагировать на действия пользователя.
Тут снова нужно упомянуть про мат. часть. Кроме базы данных, есть еще несколько объектов, которые относятся не к самому документу, а к приложению в целом. Это, например, объект Application, коллекция всех документов, открытых в приложении DocumentCollection. И это объект взаимодействия с пользователем – Editor. Есть и другие, но я их сейчас не касаюсь.
У объекта Editor есть ряд методов для взаимодействия с пользователем: запрос объектов, запрос строки, числа, области. Запрос объекта осуществляется методом GetEntity(PromptEntityOptions). Объект PromptEntityOptions – это необязательные параметры. Через этот объект задается строка приглашения, ключевые слова (если нужны), выставляются ограничения на выбор объектов. Подобный объект принимают все методы ввода.
Принцип хода остался прежний – пользователь выбирает клетку, куда хочет пойти. Клетка – это объект «штриховка». Поэтому – указываю, что принимаем в качестве ввода только объекты штриховки, пустой выбор – запретить, обязательно должен быть объект. И пишем строку приглашения.
По клетке определяется, куда именно пользователь хочет поставить свою фишку. Далее алгоритм проверяет, можно ли это сделать, если да – происходит ход игрока и нужные фишки переворачиваются.
Как я уже писал – все объекты живут внутри базы данных. Это значит, что для того чтобы прочитать или изменить свойства какого-либо объекта, этот объект нужно открыть. Открытие объектов происходит методом транзакции GetObject(). По завершению изменений транзакция подтверждается.
Я сделал две структуры данных для хранения игровой доски в памяти: массив и словарь.
Массив хранит образ доски 8 на 8, а словарь соответствия элемент клетки — ObjectId-штриховки. Обе структуры данных хранят ссылки на объекты игровой доски. При таком подходе можно не заботиться о синхронизации. Меняется будет только элемент Piece. А получить его всегда можно по ссылке. Не важно из массива или из словаря.
В отличии от скриптов, на .NET многие вещи мне удалось сделать красивее и проще. Возможности фрэймворка несли приятные вкусности. К примеру, с использованием LINQ структуры данных обрабатывались почти сами собой. Подсчет количества фишек пользователя – в одну строку. Выбор клетки для хода компьютера – один запрос. Красота.

Исходники игры можно взять тут. Нужно открыть проект в Visual Studio или SharpDeveloper и скомпилировать. Пути проекта настроены с расчетом на то, что nanoCAD установлен в стандартную директорию.
Если исходники Вам не нужны, а хочется просто посмотреть на реверси, можно скачать собранный нами модуль.
Для запуска игры нужно загрузить сборку MgdReversi.dll в nanoCAD командой NETLOAD. Теперь можно запускать игру командой PLAY.
Было бы интересно в середине игры остановиться, сохранить в nanoCAD-е игру в файл, открыть файл в AutoCAD-е и доиграть, ведь формат файла в обеих системах один и тот же.
Но, для этого нужно переделать архитектуру приложения, сейчас информация о состоянии игры хранится в памяти команды, а нужно ее сохранять в объектах чертежа (поле, фишки), которые сохраняются в файл. Оставим это на будущее.
А до тех пор можно играть в Реверси не останавливаясь, от начала и до конца игры, что под AutoCAD-ом, что под nanoCAD-ом, и там, и там игра работает одинаково. Достаточно лишь пересобрать Реверси под AutoCAD, используя его SDK, ObjectARX, это не сложно.
Как известно лучший способ что-то изучить – сделать это. Когда-то я писал Реверси под nanoCAD на скрипте. Теперь я решил написать Реверси на .NET.

В результате получилось кросс-САПР-платформенное приложение, способное работать не только под nanoCAD-ом. Как это делалось — смотрите под катом.
Программировать под nanoCAD можно было и раньше. На скриптах dows писал кривые Серпинского, я писал Реверси, были и другие примеры с нашего форума. Это все, конечно, хорошо, но мало. Поэтому, следующий мой ход – .NET.
Entry level.
Первое, что нужно было сделать – создать сборку, содержащую код, исполняемый в nanoCAD:
- создаём проект: Visual C#, Class Library,
- добавляем в References библиотеки .NET nanoCAD-а: hostdbmgd.dll, hostmgd.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.MdiActiveDocument.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.ModelSpace], OpenMode.ForWrite, false) as BlockTableRecord;
Line line = new Line();
ObjectId lid = ms.AppendEntity(line); // добавляем в модельное пространство
tr.AddNewlyCreatedDBObject(line, true); // и в транзакцию
tr.Commit(); // сохраняем изменения
}
Каждый объект, добавленный в базу данных, однозначно определяется по личному коду — ObjectId. Используя ObjectId можно открывать объекты для чтения или записи.
Создание объектов 2. Полный вперед.
Отлично. Вооружившись знаниями о внутренней кухне документа, можно, наконец, начинать разрабатывать класс игровой доски. Нет доски – нет и партии. Поэтому первое, что я начал делать – рисовать клетки в пространстве документа.
Клетки я делал из штриховок. Открыв в NCadSDK.chm описание объекта Hatch (документация входит в SDK, доступный членам Клуба разработчиков), я почерпнул нужные мне знания. Третий абзац сразу сообщил мне, что штриховка состоит из петель, а список методов объекта штриховки подсказал магическое слово AppendLoop(). Вот то, что мне нужно, подумал я.
Итак, каждую клетку я строил из квадратной полилинии, которую закрашивала штриховка. Все штриховки вместе образовывали квадрат 8 на 8 клеток.
Дальше – по накатанной, все как в прошлый раз: бордюры и фишки создаю из объектов 3Dmesh. Бордюр это полигон 2 на 2 вершины. Вычисляю координаты вершин, создаю их, добавляю в сеть, сеть добавляю в модель.
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,-linehight), tr);
AddVertexToMesh(mesh, new Point3d(col*gridstep,8*gridstep,linehight), tr);
tr.Commit();
}
// содаем вершину сети
private void AddVertexToMesh(PolygonMesh PolyMesh, Point3d Pt3d, Transaction Trans)
{
PolygonMeshVertex PMeshVer = new PolygonMeshVertex(Pt3d);
PolyMesh.AppendVertex(PMeshVer);
Trans.AddNewlyCreatedDBObject(PMeshVer, true);
}
Отлично. Клетки есть, разделители есть. Нарисовать фишку теперь тоже не трудно. Формулы вычисления координат вершин шарика я взял из скриптовой версии игры. Правда, я их подправил, чтобы объект больше походил на игровую фишку реверси.
Вот, что у меня получилось в результате:

«Я полсотни третий, выхожу на квадрат».
Теперь нужно научиться реагировать на действия пользователя.
Тут снова нужно упомянуть про мат. часть. Кроме базы данных, есть еще несколько объектов, которые относятся не к самому документу, а к приложению в целом. Это, например, объект Application, коллекция всех документов, открытых в приложении DocumentCollection. И это объект взаимодействия с пользователем – Editor. Есть и другие, но я их сейчас не касаюсь.
У объекта Editor есть ряд методов для взаимодействия с пользователем: запрос объектов, запрос строки, числа, области. Запрос объекта осуществляется методом GetEntity(PromptEntityOptions). Объект PromptEntityOptions – это необязательные параметры. Через этот объект задается строка приглашения, ключевые слова (если нужны), выставляются ограничения на выбор объектов. Подобный объект принимают все методы ввода.
Принцип хода остался прежний – пользователь выбирает клетку, куда хочет пойти. Клетка – это объект «штриховка». Поэтому – указываю, что принимаем в качестве ввода только объекты штриховки, пустой выбор – запретить, обязательно должен быть объект. И пишем строку приглашения.
Editor ed = Application.DocumentManager.MdiActiveDocument.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())
{
// pieceId – это id перекрашиваемой фишки в БД
// открываем объект pieceId для изменений - OpenMode.ForWrite
PolygonMesh piece = myT.GetObject(this.pieceId, OpenMode.ForWrite) as PolygonMesh;
// присваиваем цвет в засисимости от того чья фишка
piece.Color = (player == ePlayer.Human) ? Constants.HumanColor: Constants.PcColor;
// подверждает транзакцию
myT.Commit();
}
Вкусняшки.
Я сделал две структуры данных для хранения игровой доски в памяти: массив и словарь.
Массив хранит образ доски 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();
}

Компиляция и запуск игры
Исходники игры можно взять тут. Нужно открыть проект в Visual Studio или SharpDeveloper и скомпилировать. Пути проекта настроены с расчетом на то, что nanoCAD установлен в стандартную директорию.
Если исходники Вам не нужны, а хочется просто посмотреть на реверси, можно скачать собранный нами модуль.
Для запуска игры нужно загрузить сборку MgdReversi.dll в nanoCAD командой NETLOAD. Теперь можно запускать игру командой PLAY.
Что не успел сделать.
Было бы интересно в середине игры остановиться, сохранить в nanoCAD-е игру в файл, открыть файл в AutoCAD-е и доиграть, ведь формат файла в обеих системах один и тот же.
Но, для этого нужно переделать архитектуру приложения, сейчас информация о состоянии игры хранится в памяти команды, а нужно ее сохранять в объектах чертежа (поле, фишки), которые сохраняются в файл. Оставим это на будущее.
А до тех пор можно играть в Реверси не останавливаясь, от начала и до конца игры, что под AutoCAD-ом, что под nanoCAD-ом, и там, и там игра работает одинаково. Достаточно лишь пересобрать Реверси под AutoCAD, используя его SDK, ObjectARX, это не сложно.