先ずはOpenGLの子孫の一つである、OpenTKの導入(それも最新バージョンではなく、オワコン的.NET Framework版ですが)でのトラップについて語りました。(今読み返してみると余り「何が大変?どういうトラブル?」的に軽く感じられてしまいますが、当時は、

 

フザーっ!!!

 

的に逆上していました。まぁ、glutやfreeglutの導入の時も結構なトラブルがありましたが...)

 

軽い気持ちで遊んでみようと導入したまでは良かったのですが、

 

矢張りある程度遊ぶにはOpenGLを勉強しなくちゃね!

 

という実感と、

 

とはいえ、関連記事を読めば読むほどにOpenGLの奥深さと広大さに心が折れるなぁ!!!

 

という実感が交錯してきて、既に逃げ出したくなっているのが実態ですね。

 

今後ですが、

 

1.まずOpenGLの勉強はGLUT(freeglut)で行います。その際に、

(1)前に挫折したGLUTをユーザーウィンドウで使ってみる(GLSamplerというプログラムを用意しました。)

(2)次にGLUTの豊富な基本図名()を利用して、3D CG(OpenGL)のワールド座標、ローカル座標、視野と投影などを色々と試してみる(GLUT_BCCというデモプログラムを用意しました。)

という2段階での経験談を書こうかと思っています。

:OpenGLそのものや、OpenTKにはない立方体、球体、円錐、円環、ティーポット等の基本図形がワイアーとソリッドで用意されているのがGLUTの魅力ですね。

 

2.次にGLUTとOpenTKのサンプルの相互移植などで、OpenGLの「方言問題()」に触れてみてはどうかな、と考えています。

:実はちょろっと移植しようとして、結構データ記述方法や関数・メソッド記述方法に互換性が高くないことが分かってきました。

 

但し、何度も言いますが「OpenGLの世界は広大、深遠で私如き新参者が偉そうなことを書けるわけではなく、新参者でも勉強した際の試行錯誤型の方の為になれば」という視点でのブログになること、ご高承願います。

 

前回までの「Python初体験」の続きです。(そして取り敢えず最終回となりました。)

今回も西瓜割ゲームのコードのリファクタリング(改良)ですが、纏められるところを関数に纏めましょう、というテーマです。

先ずはリファクタリング対象のコードを紹介します。
【オリジナル】
import random
import math

BOARD_SIZE = 5  #ボードの初期サイズ

def calc_distance(pos1, pos2):  #解説:これは関数の定義でしたね。
    #2点間の距離を求める
    diff_x = pos1[0] - pos2[0]
    diff_y = pos1[1] - pos2[1]
    return math.sqrt(diff_x**2 + diff_y**2)

#リファクタリング対象1
suika_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE))  #スイカの位置
player_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE)) #プレイヤーの位置

#スイカとプレイヤーの位置が異なる間、処理を繰り返す
while (suika_pos != player_pos):
    #スイカとプレイヤーの距離を表示する
    distance = calc_distance(suika_pos, player_pos)
    print("スイカへの距離:", distance)
#リファクタリング対象2
    #キー入力に応じて、プレイヤーを移動する
    c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
    current_x, current_y = player_pos
    if c == "n":
        current_y = current_y - 1
    elif c == "s":
        current_y = current_y + 1
    elif c == "w":
        current_x = current_x - 1
    elif c == "e":
        current_x = current_x + 1
    player_pos = (current_x, current_y)

print("スイカを割りました!")

最初にリファクタリング対象となるのは、同じコードを書いている乱数による位置決めの部分です。
これは次の関数を定義することでまとめられます。

#0以上size未満の範囲で、x座標とy座標を生成する
def generate_position(size):
    x = random.randrange(0, size)  # x座標
    y = random.randrange(0, size)  # y座標

これを定義すると↑のコードが、↓の様になります。

suika_pos = generate_position(BOARD_SIZE)  #スイカの位置
player_pos = generate_position(BOARD_SIZE) #プレイヤーの位置

次の対象は移動の為のキー入力部分です。

#direction にしたがって、posを移動する
def move_position(direction, pos):
    current_x, current_y = pos  #このposは↓の戻り値と同じタプルである。
    if direction == "n":
        current_y = current_y - 1
    elif direction == "s":
        current_y = current_y + 1
    elif direction == "w":
        current_x = current_x - 1
    elif direction == "e":
        current_x = current_x + 1
    return (current_x, current_y)  #戻り値はタプル

このように関数化すると、メインのコードは次のように簡素化されます。(第二引数はタプルです。)

move_position('n', (1, 1)) #(1, 1) から、北に移動

最後に残ったメインのコードも関数化すると、次の通りとなります。

def suika_wari():
    suika_pos = generate_position(BOARD_SIZE)  # スイカの座標
    player_pos = generate_position(BOARD_SIZE) # プレイヤーの座標
    #スイカとプレイヤーの位置が異なる間、処理を繰り返す
    while (suika_pos != player_pos):
        #スイカとプレイヤーの距離を表示する
        distance = calc_distance(player_pos, suika_pos)
        print("スイカへの距離:", distance)
        #キー入力に応じて、プレイヤーを移動する
        c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
        player_pos = move_position(c, player_pos)
    print("スイカを割りました!")

こうすると(これをライブラリーにする人はいないでしょうが)、西瓜割ゲームが

"suika_wari()"

とPythonに入力するだけで実行できるようになります。即ちこれがPythonの優位性で、ソフトウェア資産がライブラリーとしてどんどんたまってゆくことにより、後人は先人の資産を十二分にエンジョイすることが出来るようになっています。

「さて、次は何をやるのかな?」と思ったら、どうも公開されているコースはこれでおしまいのようです。何か消化不良感がぬぐえませんが、今後の「Python日記」はまた他の教育ソースから面白そうなものを取り上げて書いてみることにします。
 

#追記実はPython日記は予め結構書き溜めていたものを後で掲載していました。しかし、突然このコースが(消化不良感満載で)終わってしまったので、中止せざるを得ません。まだ全然Pythonの世界には入れていないのですが、仕方ないですね。

いずれにせよ、Pythonのすごい所は、

(1)C系の言語のような厳密性を緩和して、柔軟であること。

(2)作ったものが皆栄養(ライブラリー)として生かせるので、過去のソフトウェア資産が膨大となりどんどん高度な使用方法が出来ること、でしょうか?

現在はOpenGLに少し嵌っていますが、また落ち着いたらPythonに戻ってこようか(Pythonの新シリーズ)と思います。

 

それまでご機嫌良う!

So long!

再見! 

또 봐요!

前回、OpenTKの公式サイトでフリーダウンロードを推奨している「OpenTK.dllとOpenTK.GLControl.dllのバージョン不一致エラー問題」から、(感情的に)OpenTK導入に関わる癇癪玉を破裂させたので、今回はより客観的に書きたいと思います。

 

Windows 11に同梱されているC#コンパイラー(C#5)とMSCompAssによるプログラミング環境でOpenTKを利用される方は次の手順で設定を行ってください。

 

(1)OpenTK(Ver 3.3.3)OpenTK.GLControl(Ver 3.3.3)をそれぞれダウンロードします。

(2)ダウンロードしたファイルは共に"*.nupkg"というファイル拡張子が付いていますが、これはエクスプローラーの「名前の変更」を使って"*.zip"ファイルにしてください。そのままエクスプローラーで中身が見えるようになります。

(3)両圧縮ファイルとも"lib"というフォールダー、その中に"net20"というフォールダーがありますので、その中から、"OpenTK.dll"と"OpenTK.GLControl.dll"というファイルを、C#のOPenTKプログラムを作成するフォールダーにコピーしてください。

(4)プログラムを作成し、MSCompAssでコンパイルする際には、

 ①プログラムの冒頭に、

   using OpenTK;
   using OpenTK.Graphics;
   using OpenTK.Graphics.OpenGL;
   //using OpenTK.Input;

  という宣言を行ってください。

 ②"Option"ボタンで開くオプションダイアログの「参照」ボタンで、これら二つのDLLを選択してコンパイルしてください。

 

これだけでC#でOpenGLが使えるようになります。

 

と、いいたいのですが、

 

OpenGLはおろか、CGの基礎知識も乏しい私が簡単にグラフィックプログラムなど書けるわけもなく、

 

先ずはwebに散在するサンプルを動かしてみよう(テヘッ)

 

となるわけですが(注)、そこには大きく二つの問題が生じます。

注:実際は此処で最大、最恐のバージョン不一致問題に襲われるのですが...(怒)

 

(1)Visual Studioで作成されたプログラムしかないので、省略された自動作成コードを手作業で補完しなければならない

これはMSCompAssを使う際の宿命なのですが、webで見られるプログラムはVisual Studioで作られたものばかりで、Visual Studioga 自動作成する部分やファイルは自分でコードを手作業で補完しなければなりません。その為には書かれたプログラムを120%理解し、省略された部分を推定しなければなりませんが、webで見かけるサンプルはプログラム作法が大きく違うものがあることに気が付き、それはどうも、

 ①ウィンドウコントロールである"GLControl"を使ったプログラム

 ②OpenTKのGameWindowというクラスがあり、メインウィンドウをそれから派生させて作るプログラム

に大別されるようです。

 

(2)GLControlとGameWindowを使ったプログラムの違い、メリデメ、作法等を正しく理解する

要すればwebのサンプルプログラムを解析し、何をしているか理解しなければならない中で、この基本的な違いを正しく理解する必要があるわけですが、OpenTKのプログラミングを体系的にまとめたようなサイトが見当たらず、あーでもない、こーでもない、と推測、検証して理解してゆくのですが、これがなかなか手間です。

 

あっ、そうか!ChatGPT様にお願いしよう!

 

ということで、またぞろAIに頼ることになりました。以下はその問答の一部です。ご参考迄。

 

【ChatGPTにGLControlとGameWindowの違いを確認する】

User

またC#OpenTKOpenGL)について質問します。OpenTKを使う際、GLControlをメインフォームに貼り付けて使うのと、Gamewindowからメインフォームを派生させて使うのと二通りの使い方があると思いますが、それぞれのメリット、デメリットは何でしょう?

 

ChatGPT

OpenTKでのアプリケーション開発において、GLControlを使用する方法とGameWindowを用いる方法は、それぞれ異なるシナリオに最適です。それぞれの使い方のメリットとデメリットを以下にまとめます。

GLControlを使う場合

メリット:

  1. GUIとの統合が容易GLControlWindows Formsのコントロールとして動作するため、他のUI要素(ボタン、テキストボックス等)と一緒に配置しやすく、従来のフォームベースのアプリケーションにOpenGLのレンダリング機能を追加する際に便利です。

  2. デザイナーサポート: Visual Studioのデザイナーを使用して、フォーム上にGLControlをドラッグ&ドロップすることができ、GUIのレイアウトが直感的に行えます。

  3. イベント駆動プログラミング: Windows Formsアプリケーションと同様に、イベントベースのプログラミングが可能です。これにより、ユーザー入力やその他のイベントを容易に処理できます。

デメリット:

  1. パフォーマンスの限界GLControlは、他のUIコンポーネントと共存するため、レンダリングの最適化やパフォーマンスがフルスクリーンのゲームや高度なグラフィックスアプリケーションほどではないかもしれません。

  2. コンテキスト管理: OpenGLのコンテキスト管理が若干複雑になる可能性があり、マルチスレッド環境での使用や、他のグラフィックスAPIとの併用が難しい場合があります。

GameWindowを使う場合

メリット:

  1. 高パフォーマンスGameWindowはゲームやリアルタイムシミュレーションに最適化されており、レンダリングパフォーマンスが最大化されています。これにより、より複雑なシーンや高速な更新が要求されるアプリケーションで有利です。

  2. フルコントロール描画ループを完全に制御できるため、アプリケーションの振る舞いを細かくカスタマイズできます。

  3. シンプルなコンテキスト管理: OpenGLのコンテキストが自動的に管理され、より直接的なグラフィックスプログラミングが可能です。

デメリット:

  1. GUI統合の困難さGameWindowGUIコンポーネントとの統合が困難で、外部ライブラリ(例:ImGui.NETなど)の使用が必要になる場合があります。

  2. 柔軟性の欠如: Windows FormsWPFといった従来のデスクトップアプリケーションフレームワークの機能やコントロールを利用することが難しいです。

  3. プラットフォーム依存性GameWindowの挙動がプラットフォームによって異なる場合があり、クロスプラットフォーム開発での考慮が必要です。

結論

どちらの方法を選ぶかは、アプリケーションの要件と目的に強く依存します。GUIが重要な役割を果たすアプリケーションであればGLControl、パフォーマンスが重要でリアルタイムのレンダリングが求められるアプリケーションであればGameWindowが適しています。それぞれのメリットとデメリットを考慮して選択しましょう。

 

User

GLControlGamewindowの両者はクラスになっていますが、メソッドや描画手順は共通するものがあるのでしょうか?

 

ChatGPT

GLControlGameWindowは共にOpenTKライブラリで提供されているクラスで、OpenGLコンテキストを管理するための基本的な機能を提供しますが、それぞれの設計と使用法が異なります。ただし、OpenGLを使用する基本的なメソッドや描画手順には共通する部分があります。以下にその概要を示します。

共通する要素

  1. OpenGL関数の使用GLControlGameWindowのどちらを使用しても、OpenGLの関数(GL.*)は同じです。これには、バッファの生成、シェーダのコンパイル、描画コマンドの発行などが含まれます。

  2. イニシャライズ: OpenGLの環境設定やリソースの初期化は、どちらのクラスを使っても同様に行います。たとえば、テクスチャやバッファの生成、シェーダプログラムのセットアップなどです。

  3. 描画ループフレームごとの描画処理は、GLControlにおいては主にイベント(例えばPaintイベント)で行われ、GameWindowではOnRenderFrameメソッドをオーバーライドして行われます。どちらも同じOpenGLの描画コマンドを使います。

異なる要素

  1. イベントハンドリング:

    • GLControl: Windows Formsベースであるため、通常のイベントハンドリング(ClickMouseMoveなど)が使えます。また、Paintイベントが描画のトリガーとして使われます。

    • GameWindow: OpenTKが提供するOnUpdateFrameOnRenderFrameなどのライフサイクルメソッドをオーバーライドして使用します。これにより、フレーム更新や描画処理をより詳細にコントロールできます。

  2. ウィンドウ管理:

    • GLControl:他のWindows Formsコントロールと同じように、フォームに追加される一つのUI要素です。フォームのライフサイクルに従います。

    • GameWindow:独自のウィンドウを持ち、そのライフサイクルを完全にコントロールします。これには、ウィンドウの作成、表示、クローズが含まれます。

以下は、GLControlGameWindowの基本的な描画コードの例です。

  • GLControl (Windows Formsアプリケーション内):

    C#
    
    private void glControl1_Paint(object sender, PaintEventArgs e) {
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
        // 描画コマンド
        GL.Begin(PrimitiveType.Triangles);
        GL.Vertex3(0.0f, 1.0f, 0.0f);
        GL.Vertex3(1.0f, -1.0f, 0.0f);
        GL.Vertex3(-1.0f, -1.0f, 0.0f);
        GL.End();
        glControl1.SwapBuffers();
    }
  • GameWindow:

    C#
    
    protected override void OnRenderFrame(FrameEventArgs e) {
        base.OnRenderFrame(e);
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
        // 描画コマンド
        GL.Begin(PrimitiveType.Triangles);
        GL.Vertex3(0.0f, 1.0f, 0.0f);
        GL.Vertex3(1.0f, -1.0f, 0.0f);
        GL.Vertex3(-1.0f, -1.0f, 0.0f);
        GL.End();
        SwapBuffers();
    }

どちらもOpenGLの基本的な描画処理が行われていますが、イベント駆動かフレーム駆動かの違いがあります。この違いにより、どのコンテキストでどのクラスを選ぶかが決まります。

 

今回もChatGPT様の回答はとても簡潔で要を得ており、サンプルも動くようになりました。

 

が、

 

哀しい哉、私の3D CGやOpenGLに関する知見が乏しすぎて描画作法や描画方法等の基本がわかっておりません。また、OpenTKでそれらを学習しようとしても(既に旧いライブラリーであるOpenGL関連の記事は書きつくされており)OpenTKがらみでの記事は乏しいのが実態です。

 

一方、

 

3D CGやOpenGLに関する解説、学習記事等はGLUT(含freeglut、以下同じ)で豊富であり、前回書いたように

 

学習するならC++ plus GLUT(freeglut)ですやん!!

 

という方向に流れていくことになりました。

 

ということで、

 

これからは(話の端緒である)OpenTKを取り敢えず置いといて、再度このブログの主題であるBCCForm and BCCSkeltonでfreeglutを使って

 

「OpenGLの基本機能のデモプログラムを作ろう」

 

 

へと移ってゆきます。乞ご期待!

 

前回までの「Python初体験」の続きです。

前回は西瓜割りゲームの盤の大きさを定数として、変数に代入して使う

BOARD_SIZE = 5  #ボードの初期サイズ

という「プログラムの機能はそのままで、書き方を改善」しましたが、これを「リファクタリング」というのだそうです。(注)
注:C系のプログラミングをやっていてそんな言葉は聞いたことがありませんでしたが、ググると矢張り直ぐに出てきますね。

今回のリファクタリングは、データをその情報の目的に合わせて適当な型にする、というお話です。具体的には、プレーヤーと西瓜の位置を(x, y)というタプルにして使い、関数もそれに合わせて変更する、ということをしています。
しかし、プレーヤーの位置データは変化するので、不可変のタプルとすることで毎回再代入によるタプルの更新を行っています。

#【Python初体験のコード】西瓜割りゲーム1
import random
import math

BOARD_SIZE = 5  # ボードの初期サイズ

def calc_distance(pos1, pos2):  #解説:これは関数定義です
    # 2点間の距離を求める
    diff_x = pos1[0] - pos2[0]
    diff_y = pos1[1] - pos2[1]
    
    return math.sqrt(diff_x**2 + diff_y**2)

suika_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE))  # スイカの位置
player_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE)) # プレイヤーの位置

# スイカとプレイヤーの位置が異なる間、処理を繰り返す
while (suika_pos != player_pos):

    # スイカとプレイヤーの距離を表示する
    distance = calc_distance(suika_pos, player_pos)
    print("スイカへの距離:", distance)
    
    # キー入力に応じて、プレイヤーを移動する
    c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
    current_x, current_y = player_pos

    if c == "n":
        current_y = current_y - 1
    elif c == "s":
        current_y = current_y + 1
    elif c == "w":
        current_x = current_x - 1
    elif c == "e":
        current_x = current_x + 1

    player_pos = (current_x, current_y)

print("スイカを割りました!")

#【playerをリストにする】西瓜割りゲーム2
import random
import math

BOARD_SIZE = 5  #ボードの初期サイズ

def calc_distance(pos1, pos2):  #解説:これは関数定義です
    # 2点間の距離を求める
    diff_x = pos1[0] - pos2[0]
    diff_y = pos1[1] - pos2[1]
    
    return math.sqrt(diff_x**2 + diff_y**2)


suika_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE))  #スイカの位置-タプル
player_pos = [random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE)] # プレイヤーの位置-リスト

#スイカとプレイヤーの位置が異なる間、処理を繰り返す
while (suika_pos != player_pos):

    #スイカとプレイヤーの距離を表示する
    distance = calc_distance(suika_pos, player_pos)
    print("スイカへの距離:", distance)
    
    #キー入力に応じて、プレイヤーを移動する
    c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
    if c == "n":
        player_pos[1] += -1
    elif c == "s":
        player_pos[1] += 1
    elif c == "w":
        player_pos[0] += -1
    elif c == "e":
        player_pos[0] += 1

print("スイカを割りました!")

<出力>
スイカへの距離: 2.23606797749979
n:北に移動 s:南に移動 e:東に移動 w:西に移動n
スイカへの距離: 1.4142135623730951
n:北に移動 s:南に移動 e:東に移動 w:西に移動n
スイカへの距離: 1.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動w
スイカを割りました!
スイカへの距離: 2.23606797749979
n:北に移動 s:南に移動 e:東に移動 w:西に移動n
スイカへの距離: 3.1622776601683795
n:北に移動 s:南に移動 e:東に移動 w:西に移動s
スイカへの距離: 2.23606797749979
n:北に移動 s:南に移動 e:東に移動 w:西に移動s
スイカへの距離: 1.4142135623730951
n:北に移動 s:南に移動 e:東に移動 w:西に移動s
スイカへの距離: 1.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動e
スイカを割りました!
スイカへの距離: 2.23606797749979
n:北に移動 s:南に移動 e:東に移動 w:西に移動n
スイカへの距離: 3.1622776601683795
n:北に移動 s:南に移動 e:東に移動 w:西に移動s
スイカへの距離: 2.23606797749979
n:北に移動 s:南に移動 e:東に移動 w:西に移動s
スイカへの距離: 1.4142135623730951
n:北に移動 s:南に移動 e:東に移動 w:西に移動e
スイカへの距離: 1.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動s
スイカへの距離: 0.0




上の出力結果を見ていただくとお分かりですが、while文

while (suika_pos != player_pos):

の条件式(suika_pos != player_pos)が、同じ位置であっても(「タプルとリストの相違」が原因ではないかと思いますが)falseになりません。(注)
注:スイカへの距離を表示した後に"print("suika_pos != player_pos", suika_pos != player_pos)"を入れて検証しました。
    <出力>
    スイカへの距離: 0.0
    suika_pos != player_pos True

ということで、最終的に"distance != 0"を条件文として以下の部分だけ変更しました。

【修正部分】
#スイカとプレイヤーの位置が異なる間、処理を繰り返す
distance = calc_distance(suika_pos, player_pos)
while(distance != 0):
    #スイカとプレイヤーの距離を表示する
    print("スイカへの距離:", distance)
    
    #キー入力に応じて、プレイヤーを移動する
    c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
    if c == "n":
        player_pos[1] += -1
    elif c == "s":
        player_pos[1] += 1
    elif c == "w":
        player_pos[0] += -1
    elif c == "e":
        player_pos[0] += 1
    distance = calc_distance(suika_pos, player_pos)

print("スイカを割りました!")

なお、C系のように

while((distance = calc_distance(suika_pos, player_pos) != 0):

という代入文込みの条件式を書いたのですが、シンタックスエラーではねられましたね。
<修正後出力>
スイカへの距離: 1.4142135623730951
n:北に移動 s:南に移動 e:東に移動 w:西に移動e
スイカへの距離: 1.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動s
スイカを割りました!

ではでは。
 

ps. 長らくお付き合いいただいた[Python日記」も次回が最終回となります。

 

本来は【OpenGL】シリーズの話題なのですが、内容がプログラミングアカデミックではないので、【無駄話】で書きます。

 

元々この【OpenGL】シリーズは、C#で「OpenTKを使ってみよう!」というところから始まったのですが、やっとこさOpenTKを導入して、webにあるサンプルプログラムを動かせるようになると、余りに自分がOpenGLについて無智であることが哀しく、恥ずかしく、「少しはお勉強を...」と思い立ったのは良いのですが、OpenTKは新しいライブラリーですがWin32のglutの様に基本図形を簡単に使えず、フリーデータを読み込むか、自分で打ち込むか(無理っ!!!)しかない為、

 

学習するならC++ plus GLUT(freeglut)ですやん!!

 

と、(取り敢えずOpenTKは私が経験値を稼いで、レベルアップするまでスタックにpushしておこうと)方針変換をしたこと、前に書きました。

 

その時点では、

 

 

こんな感じでしたが、その後研鑽を積み、修行に励んだので、大分3D CGのことも分かってきまして、現在は

 

 

この程度になり、そろそろネタとして書けるような気がしてきました。あと少し【Python日記】があり、OpenTKネタがあるのですが、その後この(久々のBCCSkeltonによる)Win32ベースのアンマネージドプログラムを紹介できる日が来ると思いますのでお楽しみに。

 

前回書いたように、【OpenGL】シリーズは

 

ホントーに、アッタマ来たっ!!!

 

と感じた表記から始めましょう。

 

「OpenTK導入格闘記」とあるように、C#でOpenGLを使うユーティリティライブラリー(Tool Kit、という名ですが)があるということで、ちょっと調べてみたのが運の尽き、というお話です。

 

ここで書いていますが、

 

大本山はここ

 

ということは直ぐに分かりました。

 

1.OpenTKとは
OpenTKとはOpen Tool Kitの意味らしく、C#で「OpenGL、OpenCL、OpenAL(」)のラッパーライブラリーです。
:(1)OpenGL(Open Graphics Library)-クロスプラットフォームのグラフィックスハードウェア向けの2次元/3次元コンピュータグラフィックスのAPI。
(2)OpenCL(Open Computing Language)-クロスプラットフォームのは、マルチコアCPUやGPU、Cellプロセッサ、DSPなどによる異種混在の計算資源を利用した並列コンピューティングのためのAPI。
(3)OpenAL(Open Audio Library)-クロスプラットフォームのオーディオAPI。
 

要すれば、C#でOpenGLを使う為の(ほぼ唯一の)ライブラリーということです。
 

2.ダウンロード
MSCompAssは最新の.NETのC#ではなく、Version 4.8.9032.0 for C#5なので、OpenTKも(大本山のご指示通り、更新されないオワコンの感がある).NET Framework向けのVersion 3.xを使用しました。(OpenTK.GLControlはVersion 3.3.3)



しかし、様々なWebの記事ではVisual Studioを使ってダウンロードした圧縮ファイル(*.nupkg)を展開しており、ダウンロードしたファイルを覗いても(苦手な)xamlファイルや不明の拡張子のついたファイルが色々あって、正直取り扱いに恐怖を覚えました。
:「なんだこりゃ?」と思いましたが、何のことはない。*.zipに書き換えるとzipファイルになります。
 

3.最初の躓き(どのようにOpenTKを使えばよいのか?)
どうやって使うのかについて調べるとほとんどの記事がVisual Studioベースで、(レジスター登録などが勝手に行われるとまた厄介なので)MSCompAssで単にDLLを参照するだけでよいのかどうか分からず、二の足を踏んでいました。

一旦は臍を噛みながら断念しようかと考えましたが、「単にダウンロードして、その中のdllを取り出して参照すればよい」と(後から考えればその通りなんですが)極めて簡潔、明快に書かれているある(やはりcsc.exeを単独で使う好き者の方の)Web記事を見つけてダウンロードを決意しました。

 

4.次の(致命的)躓き(OpenTKとOpenTK.GLControlのバージョン不一致問題)
大本山の.NET Framework用のVersion 3.xという奴はクリックするとVersion3.3.1で、

併用すべきOpenTKのGLControlというファイルはVersion 3.3.3になっていましたが、

「大本山がVersion 3.xの最終作を掲示しているのだから」と別に問題意識もなくダウンロードしました。そしてネットに出ているサンプルプログラム(GLControlを使うプログラム-注1)をコンパイルすると、

 

なんと、エラーになる!注2

 

じゃないですか?

注1:OpenTKは、ウィンドウコントロールで描画する為の"GLControl"と、メインウィンドウ自体でOpenGLが使える"GameWindow"という、二つの使い方があります。今回のエラーはGameWindowを使うプログラムではなく、GLControlを使うプログラムだけ発生しました。

注2:エラーの内容は、

"error CS1705: アセンブリ 'OpenTK.GLControl, Version=3.3.3.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4' は、参照されているアセンブリ 'OpenTK, Version=3.3.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4' よりも新しいバージョンを含む 'OpenTK, Version=3.3.3.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4' を使用します。(「んなもんねーよっ!」と腹が立ちました。)
OpenTK.dll: (以前のエラーに関連するシンボルの位置)
"
というもので、大本山の公開しているバージョンでは絶対にコンパイルできないことを意味します。

 

んな訳ないだろうっ!!!

 

ということで、様々なサイトを訪れ、エラー検索し、調べまくったが対応方法が分からりません。英文サイトでそれらしきものを見つけましたが、結局的「外れ」で、最後にCharGPT様々に縋ることになりました。

 

【OpenGPTとの問答】

User

C#コンパイラー(csc.exe C#5)に関する質問です。

ChatGPT

C#コンパイラ(csc.exe)について質問があれば、どうぞお聞かせください。C# 5.0に関する機能や、コンパイラの使用方法、エラー対応、またはその他何でもお答えしますので、具体的な質問を教えていただけると助かります。

User

OpenTKGLControlを使おうとすると、

"error CS1705: アセンブリ 'OpenTK.GLControl, Version=3.3.3.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4' は、参照されているアセンブリ 'OpenTK, Version=3.3.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4' よりも新しいバージョンを含む 'OpenTK, Version=3.3.3.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4' を使用します

OpenTK.dll: (以前のエラーに関連するシンボルの位置)"

というエラーが出ました。どのように対処すればよいでしょうか?

ChatGPT

提示されたエラーメッセージによると、OpenTKのプロジェクトで使用しているOpenTK.GLControlアセンブリがバージョン3.3.3.0であり、参照されているOpenTKアセンブリがバージョン3.3.1.0であるというバージョンの不一致が発生しています。これはOpenTK.GLControlが要求するOpenTKのバージョンがプロジェクトに含まれているバージョンより新しいということを意味します。

この問題を解決するためには、次のようなステップで対処できます:

バージョンの確認と更新

プロジェクトで使用しているOpenTKOpenTK.GLControlのバージョンを確認し、両方とも同じバージョン(この場合は3.3.3.0)に統一します。これは、NuGetパッケージマネージャーを使用して更新することができます。

NuGetパッケージの更新

Visual Studioの場合、ソリューションエクスプローラーでプロジェクトを右クリックし、「NuGetパッケージの管理」を選択します。

「アップデート」タブをクリックして、利用可能なアップデートを確認します。

OpenTKOpenTK.GLControlのパッケージを最新バージョンにアップデートします。

プロジェクトのリビルド

パッケージの更新後、プロジェクトをリビルドしてみてください。これにより、すべてのアセンブリが最新のバージョンに基づいて正しくリンクされるようになります。

依存関係の問題が解決しない場合:

パッケージをアンインストールし、再度インストールすることも検討してください。

依存関係の問題が引き続き発生する場合は、プロジェクトファイル(.csproj)を手動で開いて、関連するパッケージリファレンスのバージョンが正しく指定されているかを確認してください。

これらのステップを試してみてください。それでも問題が解決しない場合は、さらに具体的な設定やエラー情報を教えてください。

User

先ずOpenTK.dllにはバージョン3.3.3.0が存在しません。3.3.1.0が最新)従って依存関係を修正しなければなりませんが、Visual Studio(従って.csprojファイルやMSBuild)を使っていないので、csc.exeに与えるオプションを教えてください。

CharGPT

OpenTK.dllの最新バージョンが3.3.1.0であり、それに対してOpenTK.GLControl3.3.3.0を要求している場合、実際にはGLControlのバージョンを下げる必要があるか、あるいは存在しないバージョンに対する間違いがある可能性が考えられます。Visual Studioを使用していない場合、コマンドラインでcsc.exeを使ってコンパイルする際のオプション設定について説明します。

まず、正しいバージョンのDLLファイルを指定するために、次のようにコマンドラインからコンパイルを行います:

必要なDLLを指定する: コンパイル時に参照DLLとしてOpenTK.dllOpenTK.GLControl.dllを明示的に指定する必要があります。また、それぞれのDLLが適切なフォルダにあることを確認してください。

csc.exeのコマンドラインオプション: csc.exeでプロジェクトをコンパイルする基本的なコマンドラインは以下の通りです。パスは環境によって異なるため適宜読み替えてください。

csc.exe -reference:パス\OpenTK.dll;パス\OpenTK.GLControl.dll -out:アプリケーション名.exe ソースファイル名.cs

ここで、-referenceオプションで使用するDLLを指定し、-outで出力する実行可能ファイルの名前を指定します。

例:

csc.exe -reference:C:\libs\OpenTK.dll;C:\libs\OpenTK.GLControl.dll -out:MyApplication.exe Program.cs

このコマンドはProgram.csというソースファイルをコンパイルし、MyApplication.exeという名前の実行ファイルを生成し、C:\libs\ディレクトリにあるOpenTK.dllOpenTK.GLControl.dllを参照します。

注記: もしOpenTK.GLControl.dllがバージョン3.3.3.0を要求しているにも関わらず、実際には3.3.1.0しか存在しない場合、GLControlのバージョンを見直すか、そのバージョンが誤っている(または誤って表示されている)可能性があるので、パッケージの配布元に確認することも検討してください。

User

現在OpenTKが発行しているNetFrame用のOpenTK.dll3.3.1.0OpenTK.GLControl.dll3.3.3.0しかありません。従って他のdllは使えません。仮にコンパイルする際にxamlファイルで<dependentAssembly>

<assemblyIdentity><bindingRedirect>で回避できるのであれば、XamlファイルのサンプルとMSBuildの使い方を教えてください。

ChatGPT

Visual StudioMSBuildを使用していない場合に、手動でapp.configファイルにバインディングリダイレクトを設定する方法について説明します。この設定により、特定のバージョンのアセンブリを要求する際に、異なるバージョンのアセンブリが代わりに使用されるよう指示することができます。

app.configファイルにバインディングリダイレクトを追加

app.config ファイルの作成: ソースファイルがあるディレクトリに app.config ファイルを新規作成します。

バインディングリダイレクトの追加:

app.config ファイルに以下の内容を追加します。ここで、oldVersion にはリダイレクトする元のバージョン範囲、newVersion には使用する新しいバージョンを指定します。

xml

Copy code

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<runtime>

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<dependentAssembly>

<assemblyIdentity name="OpenTK" publicKeyToken="bad199fe84eb3df4" culture="neutral" />

<bindingRedirect oldVersion="0.0.0.0-3.3.3.0" newVersion="3.3.1.0" />

</dependentAssembly>

<dependentAssembly>

<assemblyIdentity name="OpenTK.GLControl" publicKeyToken="bad199fe84eb3df4" culture="neutral" />

<bindingRedirect oldVersion="0.0.0.0-3.3.3.0" newVersion="3.3.3.0" />

</dependentAssembly>

</assemblyBinding>

</runtime>

</configuration>

この設定は、アプリケーションがOpenTKのどのバージョンを要求しても、3.3.1.0を使用するようにリダイレクトし、OpenTK.GLControl3.3.3.0をそのまま使用します。

プロジェクトのコンパイル:

csc.exe を使用してコンパイルを行い、生成された実行ファイルが app.config ファイルと同じディレクトリに存在することを確認します。そして、実行ファイルの名前が MyApplication.exe の場合、app.config MyApplication.exe.config にリネームします。

アプリケーションの実行:

コンパイルされたアプリケーションを実行する際には、リダイレクトの設定が読み込まれ、アセンブリのバージョン不一致が解消されるはずです。

これにより、アセンブリのバージョン不一致問題を解決することができます。ただし、実際にリダイレクトが適切に機能するかどうかは、プロジェクトの具体的な依存関係や実行環境によります。エラーが解消されない場合は、さらに詳細な設定調整が必要になることもあります。

User

このapp.configファイルはどのようにcsc.exeで使うのですか?

ChatGPT

実際には、csc.exe は直接 app.config ファイルを扱うことはありません。csc.exe はソースファイルからアセンブリ(例えば、.exe または .dll ファイル)を生成するコンパイラですが、設定ファイル(app.config)は実行時に .NET アプリケーションによって読み込まれます。

つまり、コンパイル時に csc.exe app.config を直接使用するわけではなく、アプリケーションの実行時に .NET ランタイムが対応する .config ファイルを使用します。そのため、コンパイル後のステップで app.config ファイルを正しく設定し、適切な場所に配置する必要があります。

コンパイル後の手順

アプリケーションのコンパイル:

csc.exe を使用してコンパイルを行います。

bash

Copy code

csc.exe -reference:OpenTK.dll;OpenTK.GLControl.dll -out:MyApplication.exe YourSourceFile.cs

app.config ファイルの配置:

コンパイルが完了したら、作成した app.config ファイルを、コンパイルで生成された実行可能ファイル(.exe)と同じディレクトリに配置し、ファイル名を <アプリケーション名>.exe.config に変更します。たとえば、実行ファイルが MyApplication.exe なら、設定ファイルは MyApplication.exe.config という名前にします。この操作により、アプリケーションを実行したときに .NET ランタイムが設定ファイルを読み込み、指定されたバインディングリダイレクトに従ってアセンブリをロードします。

重要な点

アセンブリのバージョンリダイレクトは、特定の問題を解決するための一時的な対処策として使用できますが、可能であれば、使用するアセンブリのバージョンを整合させることが最も良い解決策です。

.config ファイルは、アプリケーションの実行時設定を管理するためのものであり、コンパイラが直接操作するものではありません。

コンパイルとは無関係に、アプリケーションの実行環境において正しい設定ファイルが必要とされます。

User

しかし、csc.exeのコンパイル段階でエラーが出るので実行ファイルを生成できません。従って.configファイルも利用できません。どうすればよいでしょう。

ChatGPT

csc.exeのコンパイル段階でアセンブリのバージョン不一致によるエラーが発生している場合、そのエラーはコンパイル時に解決する必要があり、実行時の.configファイルでは対応できません。エラーを回避するためのアプローチは、以下の通りです:

1. アセンブリのバージョンを統一する

もし可能であれば、プロジェクトで使用されているOpenTKOpenTK.GLControlのバージョンを統一するのが最も簡単な解決策です。しかしながら、おっしゃるように、これらのバージョンが異なる場合、代わりの対策が必要です。

2. 代替のアセンブリを探す

もしOpenTK.GLControlの異なるバージョンが存在するなら(例えば、以前のバージョンでOpenTK 3.3.1.0と互換性のあるもの)、そのバージョンを探して使用することを検討してください。これは、バージョン不一致を解決し、コンパイルを可能にします。

3. ローカルでのアセンブリの再構築

ソースコードが利用可能であれば、OpenTK.GLControlをローカルで再構築し、OpenTK 3.3.1.0に依存するように調整することも一つの方法です。これには、ソースコードの編集とビルド環境の設定が必要になりますが、バージョンの互換性を自分で制御できるようになります。

4. ソースコードから直接問題を回避

特定の機能がOpenTK.GLControlで提供されているが、バージョン不一致により問題が発生している場合、その機能を直接OpenTKを使って実装する方法を検討することができます。これは、直接的な解決策ではありませんが、プロジェクトを進行させるための一時的な回避策となり得ます。

5. 環境を変えてみる

最終手段として、異なる開発環境やツールチェーン(例えば、Visual Studio.NET Coreなど)を試してみるのも一つの方法です。これにより、異なるバージョンのアセンブリがどのように扱われるかが変わる場合があります。

結論

これらの方法の中から適切なものを選択し、プロジェクトの要件と互換性を考慮に入れることが重要です。特に、ライブラリのソースコードへのアクセスが可能であれば、ローカルでの再構築が最も確実な解決策となるかもしれません。また、コミュニティや公式のサポートフォーラムで助言を求めるのも良いでしょう。

User

OpenTK.GLContorol.dllversion3.3.1.0に書き換える方法はありませんか?

ChatGPT

.dllファイルのバージョン番号を直接書き換えることは、一般的には推奨されません。なぜなら、アセンブリ自体の内部構造や依存関係が本当に一致しているか保証されないからです。しかし、特定の状況下で、アセンブリのメタデータを変更する必要がある場合、いくつかの方法があります。

アセンブリのバージョンを書き換える方法

IL (Intermediate Language) の編集:

アセンブリのILコードを編集することで、バージョン番号を変更することが可能です。これを行うためには、IL Disassembler (ILDasm) IL Assembler (ILAsm) が必要です。これらのツールは .NET SDK に含まれています。

ILDasmを使用してアセンブリをILコードに変換します。

出力されたILコードを編集し、アセンブリのバージョン番号を変更します。

ILAsmを使用してILコードからアセンブリを再構築します。

直接バイナリ編集:

非常にリスクが高く、通常は推奨されない方法ですが、バイナリエディタを使用してDLLファイルを直接開き、バージョン番号が格納されている部分を探して書き換えることも技術的には可能です。

アセンブリの再コンパイル:

ソースコードが利用可能な場合、アセンブリの情報ファイル (AssemblyInfo.cs または類似のファイル) でバージョン番号を変更してから、ソースからアセンブリを再コンパイルすることができます。

注意点と推奨

アセンブリのバージョンを変更することは、そのアセンブリが他のコンポーネントとどのように相互作用するかに影響を与える可能性があります。特に、バージョン番号がAPIの互換性やセキュリティチェックに使われている場合、問題が発生することがあります。

ライブラリのバージョンを変更する前に、その変更がライセンス条項や著作権に違反しないことを確認してください。

実際にバージョンを変更する前に、可能であればライブラリの開発者やメンテナーに問い合わせて、この問題の解決策についてアドバイスを求めることをお勧めします。

バージョンの書き換えが必要な場面はまれですが、他に解決策がない場合は、これらの方法を慎重に検討してください。

 

ChatGPTの回答を要すれば、

OpenTK.GLControl.dllのVersionが3.3.3なら、OpenTK.dllnoVersionも3.3.3でなければ、基本的にコンパイルできない。

という

死刑宣告

でした。

 

ということで

 

OpenTKの使用は断念しよう(泣;)

 

と思い、最後にもう一回だけwebを検索してみると、再検索の結果nugetの別の場所でOpenTK Version 3.3.3.0を見つけることが出来、

最終的な結解決に至りました。

 

なんてこったい!(でも、終わりよければすべてよし!)

 

GLControlを使ったプログラムもこれで無事、正常にコンパイルすることが出来るようになりました。やれやれ。

 

<問題点>
OpenTKの公式サイトでOpenTK Version 3.3.1.0とOpenTK.GLCOntrol 3.3.3.0を公開しているが、これはC# 5コンパイラーがVersion不一致エラー(error:CS1705)を起こす。実際にはOpenTK Version 3.3.3.0が nuget 上にあるが(https://www.nuget.org/packages/OpenTK/3.3.3)、これは余程注意深く検索しないと見つけられない。

 

この問題で多くの.NET FrameworkベースのユーザーはOpenTKの利用をあきらめてしまうのではないでしょうか?OpenTKサイトの運営者の方には注意を促したいですね。

 

前々回C#用のOpenGLライブラリー、OpenTKの導入格闘記を考えたのですが、それを書くには「3D CGやOpenGLの一定の知見」が無ければならず、それらをwebで眺めていたらサンプルが豊富な旧いglutが面白く感じ始めて(また旧いライブラリーなのでBCCSkeltonとの相性も良く)、C++でちょっと書きたいなと心が揺れ始めたことを前回書きました。(参考:末尾<参照>

 

これではイカンっ!!!

 

ということで、これらを一つのテーマに纏めてきちんと構成しよう、というのが本日の記事(というか私の備忘)です。

 

まず、全体に共通するのは

 

OpenGL

 

ということでよろしいかと思います。なので、「括りネタ」としてこれを掲げました。(注)

注:とはいっても、私、OpenGLはおろか、DirectX、その他2D・3D CGには疎く、「これは一つ苦手克服のために、少しづつでも3D CGをOpenGLの概念で学習してゆこう」という背景の考えがある為で、既にご存じの方は「なーんて低レベルっ!!!」と思われるかもしれませんが、「一緒に学習してゆこうネタ」だと思ってお付き合いしてください。

 

今後の進め方としては、(【無駄話】のタイトルでしたが)Win32API C++用OpenGLライブラリーのGLUT導入格闘記を既に書いているので、同様に

 

C#用OpenGLライブラリーのOpenTK(注)の導入格闘記

注:と言っても.Net Framework用のオワコン旧バージョン(3.x)ですが。(因みに現在はVer 4.xで、5も出るらしいです。)

 

から始めて、実際に私の「心があっちゃ、こっちゃ」したように、

 

OpenGLの基礎知識や概念、用語

 

などをBCCSkeltonのGLUTツール(注)を使って画像を紹介しながら、

注:実際現在開発中ですが、まだOpenGLを学習中なので、ツールに何を、どうさせるかまだ分からない状態にあります。(笑)なお、GLUTのよい(すごい)ところは、ソリッド・ワイアーフレームの基本オブジェクトがいくつもあるのでそれを基に3D CGを学習できることです。コントロールダイアログが未だ何もできていませんが、開発中のツール画面を載せます。

 

最終的にOpenTKのプログラミング紹介(GLControl版とGameWindow版でそれぞれ3-  4個づつ既にサンプルがあります)に行ければと考えています。(うまくゆくかな?)

 

取り急ぎ、新しい【OpenGL】シリーズの第一回とさせていただきました。

 

<参照>

 

(1)【無駄話】...浮気...?→OpenGLとの接触?

(2)【無駄話】どつぼのOpenGL...glutからfreeglutへ→glutとfreeglutの導入格闘記です。

(3)【無駄話】リベンジならず...glut→ユーザーウィンドウでの表示に限界、glutは専用ウィンドウで楽しもう。

(4)【無駄話】次のネタ、というか連載もの?→一旦glutライブラリーで懲りたOpenGLをOpenTKで始める?

(5)【無駄話】心があっちゃ、こっちゃ→OpenTKで書こうと思ったらまたglutのネタが面白く感じ始めました。

 

前回までの「Python初体験」の続きです。

(実はこれ、一回誤って削除した原稿を思い出しながら書き直しました。)
コレクションのアンパックはC系言語を使っていた人にはやや物珍しいのではないでしょうか?(と、私は感じますが。)

シーケンスなコレクション(順に並んだデータ集合、と言い換えてもよいかと思います)であるリストやタプルでは、コレクションの特定の要素を添字をつけて呼び出すことが可能でした。

#サンプルプログラム
#リスト
list = [1, 2, 3,]
#タプル
tuple = ('a', 'b', 'c')

print("list :", list)
print(list[0], list[1], list[2])
print("tuple :", tuple)
print(tuple[0], tuple[1], tuple[2])

コレクションは一挙に要素を代入することもできるそうです。

#↑と同じ出力となる
first, second, third = list
print("list :", list)
print(fiwrst, second, third)

el1, el2, el3 = tuple
print("tuple:", tuple)
print(el1, el2, el3)

更に面白いのは、関数で複数の戻り値を簡単にコレクションで返せることです。

def total_and_average_and_number(values):
 

    total = 0 #合計値を初期化する
    num = len(values)  #データの件数を求める
    for value in values:  #valuesはコレクション
        total = total + value
    average = total / num  #平均値を求める

    #複数の戻り値をコレクション(タプル)で返す
    return (total, average, num)

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

r1, r2, r3 = total_and_average_and_number(numbers)
print(r1, r2, r3)

【出力】
list : [1, 2, 3]
1 2 3
tuple : ('a', 'b', 'c')
a b c
list : [1, 2, 3]
1 2 3
tuple: ('a', 'b', 'c')
a b c
55 5.5 10

次の例題はゲームを書くというので期待しましたが、要すれば乱数と平方根で西瓜までたどり着くゲームです。

#西瓜割ゲーム
import random  #乱数使用の為
import math  #平方根を求める為

def calc_distance(x1, y1, x2, y2):
    #2点間の距離を求める
    diff_x = x1 - x2
    diff_y = y1 - y2
    
    return math.sqrt(diff_x**2 + diff_y**2)


suika_x = random.randrange(0, 5)  #スイカのx座標
suika_y = random.randrange(0, 5)  #スイカのy座標

player_x = random.randrange(0, 5) #プレイヤーのx座標
player_y = random.randrange(0, 5) #プレイヤーのy座標

#スイカとプレイヤーの位置が異なる間、処理を繰り返す
while (suika_x != player_x) or (suika_y != player_y):

    #スイカとプレイヤーの距離を表示する
    distance = calc_distance(player_x, player_y, suika_x, suika_y)
    print("スイカへの距離:", distance)
    
    #キー入力に応じて、プレイヤーを移動する
    c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
    if c == "n":
        player_y = player_y - 1
    elif c == "s":
        player_y = player_y + 1
    elif c == "w":
        player_x = player_x - 1
    elif c == "e":
        player_x = player_x + 1

print("スイカを割りました!")

【出力】
スイカへの距離: 4.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動e
スイカへの距離: 5.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動w
スイカへの距離: 4.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動
スイカへの距離: 4.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動w
スイカへの距離: 3.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動w
スイカへの距離: 2.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動w
スイカへの距離: 1.0
n:北に移動 s:南に移動 e:東に移動 w:西に移動w
スイカを割りました!

まぁ、分かりますが、余り興奮はしませんね。
次の項に行くと「マジックナンバーを避ける」とあり、↑の西瓜割ゲームの枠数を変数でまとめるということが書かれていました。

import random
import math

BOARD_SIZE = 5  #ボードの初期サイズ(これがマジックナンバー?)

def calc_distance(x1, y1, x2, y2):
    #2点間の距離を求める
    diff_x = x1 - x2
    diff_y = y1 - y2

    return math.sqrt(diff_x**2 + diff_y**2)

suika_x = random.randrange(0, BOARD_SIZE)  #スイカのx座標 <- この行を修正
suika_y = random.randrange(0, BOARD_SIZE)  #スイカのy座標 <- この行を修正

player_x = random.randrange(0, BOARD_SIZE) #プレイヤーのx座標 <- この行を修正
player_y = random.randrange(0, BOARD_SIZE) #プレイヤーのy座標 <- この行を修正

#スイカとプレイヤーの位置が異なる間、処理を繰り返す
while (suika_x != player_x) or (suika_y != player_y):

    #スイカとプレイヤーの距離を表示する
    distance = calc_distance(player_x, player_y, suika_x, suika_y)
    print("スイカへの距離:", distance)

    #キー入力に応じて、プレイヤーを移動する
    c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
    if c == "n":
        player_y = player_y - 1
    elif c == "s":
        player_y = player_y + 1
    elif c == "w":
        player_x = player_x - 1
    elif c == "e":
        player_x = player_x + 1

print("スイカを割りました!")

解説では、
「この5のように、プログラム中にいきなり出てくる意味のわからない数字は、一般に マジックナンバー といいます。基本的には、マジックナンバーは、値を直接書くのではなく、その値の意味がわかるように、常に 変数に代入してから利用するようにします。」
とあり、これはプログラミング一般の話でPython particularなものではありませんね。
 

ps.  カウントダウンです。「Python日記」も後2回を残すだけとなりました。

前の無駄話で、次は連載の「OpenTK格闘記」をやろう、と書きました。今回フォントを小さくし、色を薄くしたのは今の気分を表しているからです。

 

「確かにOpenTKを導入する際にすったもんだしたし、ひどい目にあった」

 

のですが、この「OpenTK格闘記」の本質は「導入格闘記」であり、3D CGの格闘記でも、プログラミングの格闘記でもない、というところが、気持ちの下げ要因となっているみたいです。

 

でも、余り気にせず好きにやろうかな?

 

というところもあり、矢張り「OpenTK格闘記」は導入の所だけでもしっかりやりましょうかね。

 

一方、

 

矢張り【無駄話】で「導入格闘記」を書いたOpenGL、GLUT、freeglutですが、

今回のOpenTKでまたまた好奇心がムラムラしてきて、「こいつらとBCCSkeltonで何かしてみたい」気持ちが湧いてきました。とはいえ、文字列はANSIだけの旧いライブラリーでもあり、(更に私が3D CGのド素人ということが一番おおきいのですが、)何をどうしようかまだ見えておらず、行き当たりばったりでネタを考えているところです。

 

さーて、どうなるんでしょうか?

 

アミダクジの最終回です。今回はコードの解説:とコンパイル上の注意を書きます。

 

【GhhostLeg.cs】

/////////////////////////////////////////////////////////////////////////////////////////
//阿弥陀籤ゲーム(GhostLeg.cs)
//あみだくじ:https://ja.wikipedia.org/wiki/%E3%81%82%E3%81%BF%E3%81%A0%E3%81%8F%E3%81%98
/////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;        //Assemblyを使う為
using System.Resources;            //GhostLeg.resourcesを使う為
//解説:本プログラムにはここで示したビットマップと専用アイコンを使っていますので、前者はGhostLeg.resourcesとしてリソースファイルにしておくことが必要です。

namespace GhostLeg
{
    //アプリケーションクラス
    public class App
    {
        [STAThread]
        public static void Main()
        {
            GhostLegForm glf = new GhostLegForm();
            Application.Run(glf);
        }
    }

    //GhostLegFormクラス
    public partial class GhostLegForm : Form
    {
        //アミダクジゲーム関連フィールド
        private Amidakuji adk = new Amidakuji();        //Amidakujiクラスインスタンス
        private int numPlayers = 0;                        //縦棒数
        private int numSteps = 0;                        //段数
        private int numWin = 0;                            //当たりの縦棒
        private bool selecting = false;                    //横棒処理中フラグ
        private int BarS = -1, BarE = -1, StepSel = -1;    //夫々横棒の始点、終点、その段数(-1は未選択を意味する)
        //操作用ウィンドウコントロール
        Label label;                                    //ラベルコントロール
        Button initButton, startButton, goalButton, exitButton;    //ボタンコントロール
        //ゲーム用ウィンドウコントロール(注:複数の当たりが必要な場合はRadioButtonから変更してください。)

        //解説:↑のnumWinが一つなのも、このプログラムの仕様です。
        Button[] Players;                                //縦棒の競技者
        RadioButton[] Goals;                            //縦棒のゴール(当たりは一つだけ)
        //表示関係
        private Image image2disp = new Bitmap(32, 32);    //表示用ビットマップ(初期値:幅32 x 1、高さ32 x 1)
        private Graphics gr_image;                        //表示用ビットマップのグラフィックス
        private Image[] GLimg = new Image[9];            //アミダクジのビットマップデータ
        //UIプロンプト(解説:ユーザーに対する指示を予め変数にしておきます。)
        private string pmpt1 = "「サイズ設定」ボタンで縦棒数と段数を設定してください。";
        private string pmpt2 = "「開始」ボタンを押し、隣接する縦棒をマウスクリックして横棒を引いてください。";
        private string pmpt3 = "横棒を引き終えたら、「ゴール設定」ボタンを押してゴールを設定してください。";
        private string pmpt4 = "各縦棒のボタンを押して当落を確認してください。";

        //コンストラクター
        public GhostLegForm()
        {
            //プログラムアイコンをフォームにつける
            Assembly myOwn = Assembly.GetEntryAssembly();
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);
            //アミダクジビットマップをImage配列(GLimg)に読み込む
            ResourceManager rm = new ResourceManager("GhostLeg", myOwn);    //ResourceManagerインスタンスの作成
            for(int i = 0; i < 9; i++)
            {
                GLimg[i] = (Image)rm.GetObject("GhostLeg" + i.ToString());    //解説:ビットマップ配列を作ります。
            }
            //アミダクジフォームの設定
            this.Size = new Size(380, 400);            //初期サイズ(4列 X 8段)
            this.MinimumSize = new Size(380, 400);    //最小サイズ(4列 X 8段)(解説:「サイズ指定」も参照して下さい。)
            this.Text = "Ghost Leg(あみだくじ)";
            this.Load += Form_Load;
            this.MouseClick += MainForm_MouseClick;
        }

        /////////////////////////
        //ウィンドウイベント処理
        /////////////////////////

        //WM_CREATE処理
        private void Form_Load(object sender, EventArgs e)
        {
            //Labelの設定
            label = new Label();
            label.BorderStyle = BorderStyle.Fixed3D;
            label.AutoSize = false;
            label.Width = 260;
            label.Height = 32;
            label.Location = new Point(10, this.ClientSize.Height - label.Height - 10);
            label.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
            label.Text = pmpt1;    //プロンプト表示
            this.Controls.Add(label);
            initButton = new Button();
            initButton.Location = new Point(ClientSize.Width - initButton.Width - 10, 10);
            initButton.Text = "サイズ設定";
            initButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            initButton.Click += initButton_Click;
            this.Controls.Add(initButton);
            startButton = new Button();
            startButton.Location = new Point(ClientSize.Width - startButton.Width - 10, initButton.Height + 20);
            startButton.Text = "開始";
            startButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            startButton.Click += startButton_Click;
            startButton.Visible = false;
            this.Controls.Add(startButton);
            goalButton = new Button();
            goalButton.Location = new Point(ClientSize.Width - goalButton.Width - 10, initButton.Height + startButton.Height + 30);
            goalButton.Text = "ゴール設定";
            goalButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            goalButton.Click += goalButton_Click;
            goalButton.Visible = false;
            this.Controls.Add(goalButton);
            exitButton = new Button();
            exitButton.Location = new Point(ClientSize.Width - exitButton.Width - 10, this.ClientSize.Height - exitButton.Height - 15);
            exitButton.Text = "終了";
            exitButton.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
            exitButton.Click += exitButton_Click;
            this.Controls.Add(exitButton);
            //ビットマップimage2dispのGraphicsを取得
            gr_image = Graphics.FromImage(image2disp);
        }

        //WM_L/RBUTTONDOWN処理
        private void MainForm_MouseClick(object sender, MouseEventArgs e)
        {
            //横棒選択中でなければ何もしない
            if(!selecting)    //解説:selectingが横棒を引く処理中か否かのフラグです。
                return;
            //座標の指定範囲内か否かを判別し、外なら何もしない
            Rectangle rect = new Rectangle(10, 30, numPlayers * 32, numSteps * 32);
            if(!rect.Contains(new Point(e.X, e.Y)))
                return;    //解説:この書き方はオセロゲームでも使いました。
            else    //BarS(始点)、BarE(終点)、 StepSel(その段数)
            {
                if(BarS == -1)                    //全く選択されていない状態
                {
                    BarS = (e.X - 10) / 32;        //始点x座標をBarSに設定
                    StepSel = (e.Y - 30) / 32;    //始点y座標をStepSelに設定
                    //始点のGLimgを選択状態に変更する
                    gr_image.DrawImage(GLimg[3], BarS * 32, StepSel * 32);
                    this.Invalidate();    //解説:描画命令でWM_PAINTメッセージを出します。
                }
                else                            //始点が選択されている状態(解説:0~<3~7>)
                {
                    BarE = (e.X - 10) / 32;        //終点x座標をBarEに設定
                    if(StepSel != (e.Y - 30) / 32)
                    {                            //異なる段数がクリックされた場合
                        MessageBox.Show("始点と終点の段が異なります。", "不正な選択", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    else if((BarS == BarE) || ((BarS - BarE) > 1) || ((BarE - BarS) > 1))
                    {
                        MessageBox.Show("始点と終点が同じか、または離れすぎています。", "不正な選択", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    else
                    {
                        if(BarE > BarS)
                        {
                            if(!adk.StepL2R(BarS, StepSel))    //解説:Amidakujiクラスのメソッドで戻り値はboolです。
                            {
                                MessageBox.Show("この位置は既に横棒が引かれています。", "不正な選択", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                        }
                        else
                        {
                            if(!adk.StepR2L(BarS, StepSel))    //解説:↑と同じ
                            {
                                MessageBox.Show("この位置は既に横棒が引かれています。", "不正な選択", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                        }
                    }
                    BarS = -1;
                    BarE = -1;
                    StepSel = -1;
                    DrawGhostLeg();    //アミダクジを描画する
                }
            }
        }

        //WM_PAINT処理
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            e.Graphics.DrawImage(image2disp, 10, 30);    //ビットマップを表示
        }

        //WM_CLOSE処理
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(dr == DialogResult.No)
            {
                e.Cancel = true;
            }
            else
            {
                //アミダクジImage配列を開放する
                for(int i = 0; i < 9; i++)
                {
                    GLimg[i].Dispose();
                }
                //ビットマップimage2dispとGraphicsの開放
                gr_image.Dispose();        //image由来のGraphicsを開放
                image2disp.Dispose();    //imageを開放
                //解説:描画系のリソースはアンマネージドオブジェクトが多く、必ずリリースしてください。 

           }
        }

        ///////////////////////////
        //コントロールイベント処理
        ///////////////////////////
        //「サイズ設定」ボタン処理

        private void initButton_Click(object sender, EventArgs e)
        {
            //入力ダイアログによる入力
            InputDlg inpDlg = new InputDlg();
            inpDlg.ShowDialog();
            if(inpDlg.m_Bars * inpDlg.m_Steps == 0)
            {
                MessageBox.Show("縦棒数と段数が入力されませんでした。", "確認", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            else
            {  //解説:以下はアミダクジの再初期化と再設定を行っています。
                //既存のimage由来のGraphicsとimageを開放
                gr_image.Dispose();
                image2disp.Dispose();
                //既存の縦棒関連コントロールの解放
                if(Players != null)
                {
                    for(int i = numPlayers - 1; i >= 0; i--)
                    {
                        this.Controls.Remove(Players[i]);
                        Players[i].Dispose();
                        this.Controls.Remove(Goals[i]);
                        Goals[i].Dispose();
                    }
                }
                numPlayers = inpDlg.m_Bars;
                numSteps = inpDlg.m_Steps;
            }
            inpDlg.Dispose();    //入力ダイアログの解放
            //Amidakujiクラスの初期化
            adk.Generate(numPlayers, numSteps);
            //ウィンドウサイズの再設定
            this.Height = 144 + numSteps * 32;
            //ビットマップimage2dispとGraphicsの再設定
            image2disp = new Bitmap(numPlayers *32, numSteps * 32);
            gr_image = Graphics.FromImage(image2disp);
            gr_image.Clear(this.BackColor);
            this.Invalidate();    //フォームの再描画
            //プロンプト変更
            label.Text = pmpt2;
            //「サイズ設定」ボタンを隠し、「開始」ボタンを表示
            initButton.Visible = false;
            startButton.Visible = true;
        }

        //「開始」ボタン処理
        private void startButton_Click(object sender, EventArgs e)
        {
            //縦棒関連コントロールの再設定
            Players = new Button[numPlayers];
            Goals = new RadioButton[numPlayers];
            for(int i = 0; i < numPlayers; i++)
            {
                Players[i] = new Button();
                Players[i].Location = new Point(10 + i * 32, 10);
                Players[i].Size = new Size(32, 20);
                Players[i].Text = i.ToString();
                Players[i].Click += PlayerButton_Click;
                Players[i].Enabled = false;
                this.Controls.Add(Players[i]);
                Goals[i] = new RadioButton();
                Goals[i].CheckAlign = ContentAlignment.MiddleCenter;
                Goals[i].Location = new Point(10 + i * 32, numSteps * 32 + 30);
                Goals[i].Size = new Size(32, 20);
                Goals[i].AutoCheck = false;
                this.Controls.Add(Goals[i]);
            }
            //アミダクジの描画
            DrawGhostLeg();
            //「サイズ設定」ボタンを隠し、「開始」ボタンを表示
            initButton.Visible = false;
            startButton.Visible = true;
            //プロンプト変更
            label.Text = pmpt3;
            //「開始」ボタンを隠し、「ゴール設定」ボタンを表示
            startButton.Visible = false;
            goalButton.Visible = true;
            //横棒選択フラグの設定
            selecting = true;
            //フォームの再描画
            this.Invalidate();
        }

        //「Goal設定」ボタン処理
        private void goalButton_Click(object sender, EventArgs e)
        {
            if(BarS != -1)        //横棒選択の途中で「Goal設定」に遷移した場合の処理
            {
                BarS = -1;
                BarE = -1;
                StepSel = -1;
                DrawGhostLeg();    //アミダクジを描画する
            }    //解説:ユーザーが誤操作をした場合の対応です。
            //横棒選択フラグの解除
            selecting = false;
            //乱数でゴールを設定
            Random rand = new Random((int) DateTime.Now.Ticks & 0x0000FFFF);    //乱数の初期化
            numWin = rand.Next(numPlayers);
            Goals[numWin].Checked = true;
            for(int i = 0; i < numPlayers; i++)
                Players[i].Enabled = true;
            //プロンプト変更
            label.Text = pmpt4;
            //「ゴール設定」ボタンを隠し、「サイズ設定」ボタンを表示
            initButton.Visible = true;
            goalButton.Visible = false;
        }    //解説:実質的にプログラムの処理は終わりました。(その間だけ、当たりの確認処理が行えます。)

        //終了ボタン処理
        private void exitButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        //縦棒ボタン処理(解説:各参加者の勝敗を表示する処理です。)
        private void PlayerButton_Click(object sender, EventArgs e)
        {
            int result;    //各縦棒のゴール番号用
            for(int i = 0; i < numPlayers; i++)
            {
                if((Button)sender == Players[i])
                {
                    if((result = adk.CheckResult(i)) == numWin)
                    {
                        DrawGhostLeg();
                        MessageBox.Show("Congratulations!\r\n" + i.ToString() + "番が当たりました!!!", "祝-当選", MessageBoxButtons.OK, MessageBoxIcon.Information);
                        label.Text = pmpt1;    //プロンプト初期化
                    }
                    else
                    {
                        DrawGhostLeg();
                        MessageBox.Show("残念!\r\n縦棒" + i.ToString() + "番の結果は" + result.ToString() + "番でした。", "結果発表", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                    }
                }
            }
        }

        //アミダクジ描画処理(解説:描画処理を外出しして独立メソッドにしました。)
        private void DrawGhostLeg(bool all = true, int col = 0, int row = 0)
        //all - (true)アミダクジ全体を描画する、(false)特定の位置(col, row)を描画する
        {
            if(all)                //アミダクジ全体を描画(解説:既定値なので引数を描く必要がありません。)
            {
                for(int i = 0; i < numPlayers; i++)
                {
                    for(int j = 0; j < numSteps; j++)
                    {
                        gr_image.DrawImage(GLimg[adk.GetData(i, j)], i * 32, j * 32);
                    }
                }
            }
            else                //アミダクジの指定位置を描画
            {
                gr_image.DrawImage(GLimg[adk.GetData(col, row)], col * 32, row * 32);
            }
            this.Invalidate();    //フォームの再描画
        }
    }

    ///////////////////////////
    //アミダクジ入力ダイアログ
    ///////////////////////////

    class InputDlg : Form
    {
        //ダイアログのフィールド
        public int m_Bars;
        public int m_Steps;
        //縦棒数、段数の最小値、最大値の変更は此処で行う。(/解説:4-8、8-16の設定です。)
        private int BarMin = 4, BarMax = 8, StepMin = 8, StepMax = 16;

        //ダイアログのコントロール
        Button BtnOK, BtnCancel;
        Label BarLabel, StepLabel;
        ComboBox BarBox, StepBox;

        public InputDlg()
        {
            //ダイアログの属性設定
            this.Text = "アミダクジ入力ダイアログ";
            this.ClientSize = new Size(240, 120);
            this.MaximizeBox = false;        // 最大化ボタン
            this.MinimizeBox = false;        // 最小化ボタン
            this.ShowInTaskbar = false;        //タスクバー上非表示
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        // 境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    //親ダイアログ中央に配置
            this.Load += Form_Load;
        }

        //OnLoad処理
        private void Form_Load(object sender, EventArgs e)
        {
            //コントロールの属性設定
            BtnOK = new Button();
            BtnOK.Location = new Point(20, ClientSize.Height - BtnOK.Height - 10);
            BtnOK.Text = "OK";
            BtnOK.AutoSize = true;
            BtnOK.Click += new EventHandler(OnOK_Click);
            this.Controls.Add(BtnOK);

            BtnCancel = new Button();
            BtnCancel.Location = new Point(ClientSize.Width - BtnCancel.Width - 20, ClientSize.Height - BtnCancel.Height - 10);
            BtnCancel.Text = "Cancel";
            BtnCancel.AutoSize = true;
            BtnCancel.Click += new EventHandler(OnCancel_Click);
            this.Controls.Add(BtnCancel);

            BarLabel = new Label();
            BarLabel.Text = "縦棒数";
            BarLabel.Width = 80;
            BarLabel.Location = new Point(10, 10);
            this.Controls.Add(BarLabel);

            StepLabel = new Label();
            StepLabel.Text = "段数";
            StepLabel.Width = 80;
            StepLabel.Location = new Point(10, BarLabel.Height + 20);
            this.Controls.Add(StepLabel);

            BarBox = new ComboBox();
            BarBox.DropDownStyle = ComboBoxStyle.DropDownList;
            BarBox.Location = new Point(BarLabel.Width + 20, 10);
            BarBox.MaxDropDownItems = 5;
            for(int i = BarMin; i <= BarMax; i++)
                BarBox.Items.Add(i.ToString());
            BarBox.SelectedIndex = 0;
            this.Controls.Add(BarBox);

            StepBox = new ComboBox();
            StepBox.DropDownStyle = ComboBoxStyle.DropDownList;
            StepBox.Location = new Point(StepLabel.Width + 20, BarLabel.Height + 20);
            StepBox.MaxDropDownItems = 9;    //解説:これを行わないとスクロールバーが現れます。
            for(int i = StepMin; i <= StepMax; i++)
                StepBox.Items.Add(i.ToString());
            StepBox.SelectedIndex = 0;
            this.Controls.Add(StepBox);
        }

        //OKボタン処理
        protected void OnOK_Click(object sender, EventArgs e)
        {
            if(!Int32.TryParse((string)BarBox.SelectedItem, out m_Bars))
                MessageBox.Show("縦棒数の取得に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            if(!Int32.TryParse((string)StepBox.SelectedItem, out m_Steps))
                MessageBox.Show("段数の取得に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            Close();
        }

        //Cancelボタン処理
        protected void OnCancel_Click(object sender, EventArgs e)
        {
            m_Steps = m_Bars = 0;
            Close();
        }
    }

    ///////////////////
    //アミダクジクラス
    ///////////////////

    public class Amidakuji
    {

        //解説:既にここで解説したので省略します。
    }

}

 

さて、

 

ではいよいよコンパイルです。

 

(1)先ず必要なファイルがあるか確認してください。

        

(2)MSCompAssに"GhostLeg.cs"を読み込んで(またはドロップして)、"Option"ボタンを押して「主要オプション」ダイアログを開き、リソース月にして、リソースファイルとプログラムアイコンを指定します。

(3)そしてコンパイルしてください。

 

お家でケーキやデザートを選ぶ際のアミダクジ需要に応えられれば嬉しいです。

 

ps. 上記のファイルはすべてBCCForm and BCCSkeltonパッケージのMSCompAssのDebug\Sample\GhostLegの中に入れてあります。