Nimの紹介:コンソール2048の作成



新しく、高速で、コンパイルされたもので、同時に触り心地の良いものが必要ですか? ネコにようこそ、ここでは、2048ゲームの次のクローンの実装でNimプログラミング言語をテストしますブラウザなし、ハードコアのみ、コマンドラインのみ!



プログラム内:





ニムとは誰ですか?



客観的に:



Nim-静的に型指定された、命令型、コンパイル済み。 メモリアドレスに直接アクセスし、ガベージコレクターを無効にするため、システムPLとして使用できます。 残りはここにあります



主観的に



現在登場しているプログラミング言語の多くは、1つ(または複数)のキラー機能を提供し、それらのさまざまな問題を解決しようとしています(Goのルーチン、Rustの地獄のメモリ管理など)。 Nimは特別な機能を提供しません。 これは、Pythonを連想させるシンプルなプログラミング言語です。 しかし、Nimはプログラムの作成を簡単にします。 このような高レベルのPythonとほぼ同じくらい簡単です。 同時に、コンパイルは仮想マシンのレベルではなく、マシンコードに対して行われるため、結果として得られる生産性プログラムは、対応するCのプログラムに匹敵するはずです。



NimでのOOPの外観



コードはモジュールで記述されます(つまり、Pythonスタイルのファイル)。 モジュールは他のモジュールにインポートできます。 関数( proc )があり、クラスはありません。 しかし、オーバーロードを考慮して、Uniform Function Call Syntax( UFCS )を使用してカスタムタイプを作成し、関数を呼び出すことは可能です。 したがって、次の2行のコードは同等です。



foo(bar, baz) bar.foo(baz)
      
      





また、次のコードを使用すると、クラスのないOOPを通常の意味で配置できます。



 type Game = object foo: int bar: string Car = object baz: int # * ,            # () proc start*(self: Game) = echo "Starting game..." proc start*(self: Car) = echo "Starting car..." var game: Game var car: Car game.start() car.start()
      
      





メソッドもあります。 実際、 procと同じように、違いはバインディングの瞬間のみです。 proc呼び出しは静的にリンクされています。 実行時の型情報は重要ではなくなりました。 メソッドの使用は、実行時に既存の階層内のオブジェクトの正確なタイプに基づいて実装を選択する必要がある場合に役立ちます。 そして、はい、Nimは既存の型に基づいた新しい型の作成をサポートします。単一継承のようなものですが、合成が優先されます。 詳細はこちらこちら



小さな危険があります-このようなOOPの実装は、1つのモジュール内の任意のタイプを操作するためのすべてのメソッドの物理的なグループ化を意味しません。 したがって、プログラム全体で1つの型を操作するためのメソッドを無謀に分散させることができますが、これはもちろんコードサポートに悪影響を及ぼします。



ボンネットの下の小さなC



Nimは制限までコンパイルしますが、Cでの中間コンパイルを介してそれを実行します。特定の背景があれば、Nimコードで実際に何が起こるかを見ることができるので、それはクールです。 次の例を見てみましょう。



Nimのオブジェクトは、値(つまり、スタック)および参照(つまり、ヒープ)にできます。 リンクには、 refptrの 2つのタイプがあります。 最初のタイプのリンクは、ガベージコレクターによって追跡され、ゼロカウントrefで、オブジェクトはヒープから削除されます。 2番目のタイプのリンクは安全ではなく、あらゆる種類のシステムピースをサポートするために必要です。 この例では、 参照リンクのみを考慮します。



Nimが新しいタイプを作成する一般的な方法は、次のようなものです。



 type Foo = ref FooObj FooObj = object bar: int baz: string
      
      





つまり 通常のタイプFooObjとタイプ「FooObjへのリンク」が作成されます。 次のコードをコンパイルすると何が起こるか見てみましょう。



 type Foo = ref FooObj FooObj = object bar: int baz: string var foo = FooObj(bar: 1, baz: "str_val1") var fooRef = Foo(bar: 2, baz: "str_val2")
      
      





コンパイルします:



 nim c -d:release test.nim cat ./nimcache/test.c
      
      





nimcacheフォルダー(test.c)の結果:



 // ... typedef struct Fooobj89006 Fooobj89006; // ... struct Fooobj89006 { //     FooObj. NI bar; NimStringDesc* baz; }; // ... STRING_LITERAL(TMP5, "str_val1", 8); STRING_LITERAL(TMP8, "str_val2", 8); Fooobj89006 foo_89012; //... N_CDECL(void, NimMainInner)(void) { testInit(); } N_CDECL(void, NimMain)(void) { void (*volatile inner)(); PreMain(); inner = NimMainInner; initStackBottomWith((void *)&inner); (*inner)(); } //      int main(int argc, char** args, char** env) { cmdLine = args; cmdCount = argc; gEnv = env; NimMain(); //  ""  Nim,     NimMainInner -> testInit return nim_program_result; } NIM_EXTERNC N_NOINLINE(void, testInit)(void) { Fooobj89006 LOC1; //   foo     Fooobj89006* LOC2; //  fooRef      NimStringDesc* LOC3; memset((void*)(&LOC1), 0, sizeof(LOC1)); memset((void*)(&LOC1), 0, sizeof(LOC1)); LOC1.bar = ((NI) 1); LOC1.baz = copyString(((NimStringDesc*) &TMP5)); foo_89012.bar = LOC1.bar; //  foo asgnRefNoCycle((void**) (&foo_89012.baz), LOC1.baz); LOC2 = 0; LOC2 = (Fooobj89006*) newObj((&NTI89004), sizeof(Fooobj89006)); //      fooRef (*LOC2).bar = ((NI) 2); LOC3 = 0; LOC3 = (*LOC2).baz; (*LOC2).baz = copyStringRC1(((NimStringDesc*) &TMP8)); if (LOC3) nimGCunrefNoCycle(LOC3); asgnRefNoCycle((void**) (&fooref_89017), LOC2); }
      
      





結論は次のように描くことができます。 まず、必要に応じて、コードはわかりやすく、内部で何が起こっているのかを理解できます。 次に、2つの型FooObjFooについて、Cに対応する構造体が1つだけ作成されました。変数foofooRefは、それぞれインスタンスと構造体のインスタンスへのポインターです。 ドキュメントが言うように、fooはスタック変数であり、fooRefはヒープ上にあります。



インスタンスを作成する



Nimでインスタンスを作成するには2つの方法があります。 スタック上に変数が作成される場合、 initObjName関数を使用して作成されます。 ヒープ上に変数newObjNameが作成された場合。



 type Game* = ref GameObj GameObj = object score*: int // result -   ,       proc newGame*(): Game = result = Game(score: 0) //   new(result) result.doSomething() proc initGame*(): GameObj = GameObj(score: 0)
      
      





型を使用してオブジェクトを直接作成する(コンストラクター関数をバイパスする)のは習慣ではありません。



2048



ゲームコード全体は約300行のコードに収まります。 ただし、明示的な目的がなければ、できるだけ短く書いてください。 私の意見では、これはかなり高いレベルの言語を示しています。



鳥瞰図から、ゲームは次のようになります。







コード「メイン」:



 import os, strutils, net import field, render, game, input const DefaultPort = 12321 let port = if paramCount() > 0: parseInt(paramStr(1)) else: DefaultPort var inputProcessor = initInputProcessor(port = Port(port)) var g = newGame() while true: render(g) var command = inputProcessor.read() case command: of cmdRestart: g.restart() of cmdLeft: g.left() of cmdRight: g.right() of cmdUp: g.up() of cmdDown: g.down() of cmdExit: echo "Good bye!" break
      
      





フィールドは、テキストグラフィックスとカラーコードを使用してコンソールに描画されます。 このため、ゲームはLinuxおよびMac OSでのみ動作します。 Nimでこの関数を使用すると、コンソールの奇妙な動作のため、 getch()を介してコマンド入力を行うことができませんでした。 NimのCursesは現在移植中です。利用可能なパッケージのリストにはリストされていません(ただし、パッケージは既に存在します)。 したがって、ソケットからの読み取りのブロックと追加のPythonクライアントに基づいて、I / Oハンドラーを使用する必要がありました。



この奇跡の開始は次のとおりです。



 #   1 git clone https://github.com/iximiuz/nim-2048.git cd nim-2048 nim c -r nim2048 #   2 cd nim-2048 python client.py
      
      





開発プロセスから注意したいこと。 コードが作成されて実行されるだけです! Java以外のコンパイル言語では、このような経験はありません。 さらに、 ptrポインターが使用されていない場合、記述されたコードは「安全」と見なすことができます。 構文とモジュラーシステムはPythonに非常に似ているため、中毒は最小限の時間で済みます。 既に2048のPythonの実装が完成していましたが、そのコードを最小限の修正でNimのコードに文字通りコピーして貼り付けることができたことがわかったとき、私はうれしい驚きを覚えました! もう1つの良い点-Nimにはバッテリーが付属しています。 高レベルのネットモジュールのおかげで、 ソケットサーバーコードは10行未満しか使用しません。



完全なゲームコードはgithubで表示できます。



結論の代わりに



ハンサムなニム! その上にコードを書くことは素晴らしいことであり、結果はすぐに機能するはずです。 Nimのコンパイルは、実行可能ファイルだけでなく、JavaScriptでも可能です。 この興味深い機能についてはこちらをご覧ください 。Nimで記述されJavaScript コンパイルされたNESエミュレーターはこちらで再生できます



将来的には、Nimのおかげで、高速で安全なプログラムを書くことがPythonでのプログラミングと同じくらい楽しくなり、これがコンピューターのさまざまな進行状況バーの前で費やす時間に有益な効果をもたらすことが期待されています。



All Articles