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

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

    Как известно лучший способ что-то изучить – сделать это. Когда-то я писал Реверси под nanoCAD на скрипте. Теперь я решил написать Реверси на .NET.
    nanoCAD_MgdReversi
    В результате получилось кросс-САПР-платформенное приложение, способное работать не только под 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, это не сложно.
    Нанософт
    109,00
    Компания
    Поделиться публикацией

    Комментарии 5

      0
      Реверси в nanoCAD (САПР). С одной стороны интересно, с другой стороны напоминает картинку с буханкой и автобусом.
        +2
        так понятно, что это just fun
        +2
        «Hello world!» вроде как тоже бесполезно, зато сколько радости доставляет!
        0
        Коллеги, для тех кто сходу пытается сделать пример «HelloWorld» из статьи:

        Первое, что нужно было сделать – создать сборку, содержащую код, исполняемый в nanoCAD:
        создаём проект: Visual C#, Class Library,
        добавляем в References библиотеки .NET nanoCAD-а: hostdbmgd.dll, hostmgd.dll,
        регистрируем в nanoCAD команду.

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


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


        Если после добавления библиотек в проект у вас неизвестно о том, что такое «Platform.» в команде
        Editor ed = Platform.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;

        добавьте Using
        using Platform = HostMgd;

        и не забудьте выбрать версию net framework совместимую с программой (я пробовал на 3.5)

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое