Your move, comrade .NET, or Reversi under nanoCAD again

    Some time ago, we had a great event - the release of the nanoCAD 3.5 release. The key innovation of this version was the open API, which will be discussed in this article.

    As you know, the best way to learn something is to do it. I once wrote Reversi under nanoCAD in a script. Now I decided to write Reversi in .NET.
    nanoCAD_MgdReversi
    The result is a cross-CAD platform application that can work not only under nanoCAD. How this was done - look under the cut.

    It was possible to program under nanoCAD before. On scripts dows wrote Sierpinski curves , I wrote Reversi , there were other examples from our forum. This is all, of course, good, but not enough. Therefore, my next move is .NET.

    Entry level.


    The first thing to do was create an assembly containing the code executed in nanoCAD:
    • create a project: Visual C #, Class Library,
    • add nanoCAD .NET libraries to References: hostdbmgd.dll, hostmgd.dll,
    • register in nanoCAD team.

    The method that will be registered as a command must have a public modifier and be marked with a special attribute CommandMethod.

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

    AND EVERYTHING!
    I don’t write about this in more detail, since you can read about it in the nanoCAD SDK. Where to get? At nanoCAD Developer Club , registration is open.

    Structure.


    I divided the game into several classes: game class, game board class, information panel class, game chip class:
    • the class of the game should contain algorithms for checking the ability to make a move in certain coordinates, searching for the course of the computer, counting the chips of the players, and decisions to continue the game;
    • board class - draw the board, store its contents;
    • class information panel - show the results of the passage of the party;
    • chip class - draw a chip, be able to change its color, store all the information related to a specific game cell.
    Each class should be as independent as possible.
    Next, I needed to learn how to create objects, change them and communicate with the user.

    Creation of objects. Mat. part.


    Before drawing the reverse, it was necessary to understand what to do, what to undertake.
    In order to create objects, you need to know a little about the structure of the document. Each document has a database. The database stores the objects contained in the drawing, and their relationships with each other. Everything is stored in the database: lines with arcs, model space, text styles, and much more. When adding a new object to the drawing, you need to add it to the database. And where there is a database, there are also transactions.

    Transactions are needed to protect our document: if there was a failure as a result of the code execution - objects added by this code will not get into the document - the transaction will be canceled. If everything succeeds, the transaction will be confirmed and objects will be added.

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

    Just creating an object is not enough. He will remain unconnected and hanging "in the air." The object needs to be placed somewhere. Usually this is a model space. There was something similar in the scripts - he told the model space “make a line” - it will appear there. In .NET a little differently - you need to add the created object to the model space and to the transaction.

    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();  // сохраняем изменения
    }
    

    Each object added to the database is uniquely identified by its personal code - ObjectId. Using ObjectId you can open objects for reading or writing.

    Creating objects 2. Full speed ahead.


    Excellent. Armed with knowledge of the internal kitchen of the document, you can finally begin to develop the class of the game board. No board - no party. Therefore, the first thing I started to do was draw cells in the document space.

    I made cells from hatching. Having opened the description of the Hatch object in NCadSDK.chm (the documentation is included in the SDK accessible to the members of the Developers Club ), I got the knowledge I needed. The third paragraph immediately informed me that the hatching consists of loops, and the list of methods of the hatching object suggested the magic word AppendLoop (). That's what I need, I thought.

    So, each cell I built from a square polyline, which the shading painted over. All hatching together formed a square of 8 by 8 cells.

    Further - on the thumb, everything is like last time: I create borders and chips from 3Dmesh objects. The border is a polygon 2 to 2 peaks. I calculate the coordinates of the vertices, create them, add to the network, add the network to the model.

    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);
    }
    

    Excellent. There are cells, there are dividers. Draw a chip now is also not difficult. I took the formulas for calculating the coordinates of the vertices of the ball from the script version of the game. True, I corrected them so that the object looked more like a reverse game chip.

    Here is what I got as a result:
    Game board

    "I'm fifty-three, I go out into the square."


    Now you need to learn how to respond to user actions.

    Here again, you need to mention the mat. part. In addition to the database, there are several more objects that are not related to the document itself, but to the application as a whole. This, for example, is an Application object, a collection of all documents opened in a DocumentCollection application. And this is the object of user interaction - Editor. There are others, but I don’t touch them now.

    The Editor object has a number of methods for interacting with the user: querying objects, querying strings, numbers, areas. The request for the object is carried out using the GetEntity (PromptEntityOptions) method. PromptEntityOptions object - these are optional parameters. Through this object, an invitation line is set, keywords (if necessary), restrictions on the choice of objects are set. All input methods accept a similar object.

    The principle of the move remains the same - the user selects the cell where he wants to go. A cell is a hatching object. Therefore - I indicate that we accept only hatching objects as input, the empty choice is to prohibit, there must be an object. And write the prompt line.

    
    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);

    The cell determines where exactly the user wants to put his chip. Next, the algorithm checks whether this can be done, if so, the player moves and the necessary chips are turned over.

    Repainting existing chips.


    As I already wrote - all objects live inside the database. This means that in order to read or change the properties of an object, this object must be opened. Objects are opened using the GetObject () transaction method. Upon completion of the changes, the transaction is confirmed.

    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();
     }

    Snacks.


    I made two data structures for storing the game board in memory: an array and a dictionary.
    The array stores the image of the 8 by 8 board, and the dictionary of the correspondence of the cell element is ObjectId hatching. Both data structures store references to game board objects. With this approach, you don’t have to worry about synchronization. Only the Piece element will change. And you can always get it here. It doesn’t matter from an array or from a dictionary.
    
    Dictionary GameDesc = new Dictionary();
    Piece[,] GameDesc_xy = new Piece[8, 8];

    Unlike scripts, on .NET I managed to make many things more beautiful and simpler. The capabilities of the framework carried pleasant goodies. For example, using LINQ, data structures were processed almost by themselves. Counting the number of user chips in one line. Choosing a cell for a computer move is one request. Beauty.
    int GetCounterCount(ePlayer player)
    {
      // подсчет фишек игрока player
      return gamedesk.GameDesc.Where(x => x.Value.Player == player).Count();
    }

    Game process

    Compiling and running the game


    The source of the game can be taken here . You need to open the project in Visual Studio or SharpDeveloper and compile. The project paths are configured with the expectation that nanoCAD is installed in the standard directory.
    If you don’t need the source, but just want to look at the reverse, you can download the module we have assembled .

    To start the game you need to load the assembly MgdReversi.dll in nanoCAD with the NETLOAD team. Now you can start the game with the PLAY team.

    What did not have time to do.


    It would be interesting to stop in the middle of the game, save the game as a file in nanoCAD, open the file in AutoCAD and play it out, because the file format is the same on both systems.

    But, for this, you need to redo the application architecture, now the information about the state of the game is stored in the team’s memory, but you need to save it in the drawing objects (field, chips), which are saved in a file. Let's leave it for the future.

    Until then, you can play Reversi without stopping, from the beginning to the end of the game, under AutoCAD, under nanoCAD, and there and there the game works the same way. It is enough to rebuild Reversi under AutoCAD, using its SDK, ObjectARX, it is not difficult.

    Also popular now: