前回の流れで、今回からウィンドウプログラムResourceListの開発過程をネタとしてブログを書いてゆこうと思います。

 

先ず元ネタはこれ(↓)

の中の「リソース リストの作成」に載っている抜粋コードです。

3つのコード群が載っていますが、これらは飽くまで抜粋で、これらをコピペしてコンパイルしてもエラーになります。(因みに第1のブロックは第3のブロックに書かれているリソース情報取得コールバック関数のプロトタイプ宣言です。第2のブロックは本来「コンソールプログラムならばmain関数の中に入れるべきコード」です。)

 

【今後のステップ】

(1)先ずはこれら抜粋コードを補足し、独立して完動するコンソールプログラムにしましょう。

(2)次に所謂「構造化プログラミング」ということで。リソース情報リスト部分を関数化して外出しします。

(3)更に外出しした関数をC++のお家芸でクラスオブジェクト化します。

(4)クラスオブジェクトしたものを、よりポータブルにすべく「ファイル出力」を「文字列データ」に変更します。(ファイル出力ももちろん可能です。)

現在は此処まで来ています。

 

(5)更に「種類」の出力が味気のない数字だけなので、人間にとって分かり易い文字出力に変更してみましょう。

(6)そうしてできたCRESLIST.hを使って、BCCSkeltonの器に落とし込んでみるつもりです。

 

ps. 実は上記(3)~(4)にかけて結構深刻なバグ委見舞われ、原因追及に手こずりました。結局原因はここ(↓)

にあった()のですが、なんでそんな間違いをしたのかというと、Microsoft Learnのコードが問題となる

"sizeof(szBuffer) / sizeof(TCHAR)"

を使っていたのを見逃したためでした。

:"sizeof(文字列ポインター)"はポインター変数のサイズを返すので、strlenの様に文字列長は得られません。

 

権威を軽々に信じてはいけない

 

という良い見本ですね。

 

矢張りネタがない、

出てこない。

もうここが限界か?

 

と悩む日々。しかし、「何かを作りたい」という欲求は大きく、昔のBASICプログラムにヒントがないか、C++を学んだ時の教則本にヒントがないか、終いには昔のPC雑誌やDelphi教則本()等も見たのですが、矢張りひらめきは出てきません。

:無料のdelphi 6.0が欲しくて購入。結局Borlandが事業を譲渡し、プロダクトキーが失われ現在は使えません。しかし、これは後にBCBにプログラムを移植する際に役に立ちました。

 

ということで、今度は今までwebで調べた際に後に参考にするためにとっておいた様々(且つ膨大)なURLを紐解いて色々と眺めてみるのですが、

 

ウ~ン、いまいちインスピレーションが湧いてこないなぁ。

 

と思っていた時に、Win API関連で開いたものが「Microsoft Learnの実行ファイル(*.exe)のリソースをリスト出力するサンプル」。これは今までやったこと無いし、C++(BCC Form and BCCSkelton)のネイティブWindowプログラム化も可能、ということで、ここ(↓)

の中の「リソース リストの作成」に載っている抜粋コード()を参考にして、きちんと独立して動く「ウィンドウズプログラムにしてみようか?」と考えています。

:3つのコード群が載っていますが、これらは飽くまでVisual Studioのコードの抜粋で、これらだけをコピペしてコンパイルしても山ほどエラー出ますのでご注意を。因みに第1のブロックは第3のブロックに書かれているリソース情報取得コールバック関数のプロトタイプ宣言です。第2のブロックは本来「コンソールプログラムならばmain関数の中に入れるべきコード」です。以下は抜粋コードを再構成してコンソールプログラムにして出力したものです。

 

とはいえ、出来上がっても「単に実行ファイルで使用するリソース(注)の情報リストを表示」するだけのものですが、関心のある方はそれを発展させてリソース管理や処理を行えるようになるかも、です。

:リソースの種類としてはこれを見てください。"RT_"という接頭語があるのでWin_RT関連かと思いましたが、「リソースの種類 (Winuser.h) - Win32 apps」とあるのでWindows一般と考えてよいでしょう。因みにBCC102のwinuser.hで検索すると、

/*
 * Predefined Resource Types
 */

#define RT_CURSOR           MAKEINTRESOURCE(1)
#define RT_BITMAP           MAKEINTRESOURCE(2)
#define RT_ICON             MAKEINTRESOURCE(3)
#define RT_MENU             MAKEINTRESOURCE(4)
#define RT_DIALOG           MAKEINTRESOURCE(5)
#define RT_STRING           MAKEINTRESOURCE(6)
#define RT_FONTDIR          MAKEINTRESOURCE(7)
#define RT_FONT             MAKEINTRESOURCE(8)
#define RT_ACCELERATOR      MAKEINTRESOURCE(9)
#define RT_RCDATA           MAKEINTRESOURCE(10)
#define RT_MESSAGETABLE     MAKEINTRESOURCE(11)

#define DIFFERENCE     11
#define RT_GROUP_CURSOR MAKEINTRESOURCE((ULONG_PTR)(RT_CURSOR) + DIFFERENCE)
#define RT_GROUP_ICON   MAKEINTRESOURCE((ULONG_PTR)(RT_ICON) + DIFFERENCE)
#define RT_VERSION      MAKEINTRESOURCE(16)
#define RT_DLGINCLUDE   MAKEINTRESOURCE(17)
#if(WINVER >= 0x0400)
#define RT_PLUGPLAY     MAKEINTRESOURCE(19)
#define RT_VXD          MAKEINTRESOURCE(20)
#define RT_ANICURSOR    MAKEINTRESOURCE(21)
#define RT_ANIICON      MAKEINTRESOURCE(22)
#endif /* WINVER >= 0x0400 */
#define RT_HTML         MAKEINTRESOURCE(23)
#ifdef RC_INVOKED
#define RT_MANIFEST                        24
#define CREATEPROCESS_MANIFEST_RESOURCE_ID  1
#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID 2
#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID 3
#define MINIMUM_RESERVED_MANIFEST_RESOURCE_ID 1   /* inclusive */
#define MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID 16  /* inclusive */
#else  /* RC_INVOKED */
#define RT_MANIFEST                        MAKEINTRESOURCE(24)
#define CREATEPROCESS_MANIFEST_RESOURCE_ID MAKEINTRESOURCE( 1)
#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(2)
#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(3)
#define MINIMUM_RESERVED_MANIFEST_RESOURCE_ID MAKEINTRESOURCE( 1 /*inclusive*/)
#define MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(16 /*inclusive*/)
#endif /* RC_INVOKED */

と出てきますね。

 

ps. これからは「ネタに詰まったらMicrosoft Learnを見る」というのが、あり、かもです。

 

一寸所用で2泊ほど出かけておりましたので久々の投稿ですが、前にこんなことを書いたことを覚えておられるでしょうか?

 

今朝も、

 

「12月から医療を受けられなくなると不安がっているお年寄りの方」

 

の新聞記事が出ていたので、読んでみると次のようなことです。

 

(1)マイナンバーカードを持っていないので、まずそれを自分、もしくは代理人()にとってもらう必要がある。(お年寄りがご自身で面談いいって取得するのは難しい方が多いでしょうから、ここで不安を感じますね。)

:高齢化、というのは単に「生存余命」が伸びただけのことであり、自分の力だけで動くことのできる≒自助生活ができる「自動余命」ではないので、後者の方は余り変わっていませんね。あと一か月で古希になる私(母親は101歳)が言うのだから間違いないですよ。

 

(2)マイナ保険証登録を行う。(私にはここここ迄しか行けませんが、それだけで十分「わかりずらいUI」であることが判ります。その訳は、「WEB1画面に異なる目的と内容の情報を詰め込みすぎて、ユーザーに何のために、何を、どうすればよいのか」が分からなくなる典型だからです。もっと情報を整理し、情報を絞って画面遷移と共に発展させてゆく形でないと、私の様に認知力が低下してきた老人には「見るのも嫌」ということでクローズされてしまうでしょう。)

 

(3)暗証番号を覚えていることに不安があるお年寄りの場合には「顔認証ナンバーカード」が利用できると書かれていたので、調べましたら、墨田区の説明ではこういうことらしく、医療履歴や健康状態等、より重要な機微情報を扱う保険証で使えて、マイナポータル、コンビニ交付、e-Tax等では利用できないというのは、単に開発時の環境から画像認証ID処理が出来ないシステム側の問題と考えられます。(即ち、スマホでお馴染みの、最近の顔認証が広く拡大している環境では早晩これらサービスも顔認証ナンバーカードで利用可能となるのではないでしょうか?)

 

(4)確認証との違いはこういうことらしく、ここでも厚生労働省の強圧的頑固さは単に大臣の性格だけではなく、ビッグデータを欲しがる「金満」製薬業界のロビーイングを疑ってしまいますね。

 

ではあるべきソリューションとは何なのでしょう?

 

矢張りシステムセキュリティに問題が残り、ユーザー(国民)に不安が生じている「マイナンバーカード()」から正攻法に進めたことは、(予測可能ではありましたが)結果論として余り賢い方法ではなかったように感じます。

:「マイナカード」というポピュリズムネームのマイナンバーカード(ID目的のICカード)は、平成二十五年法律第二十七号、即ち「行政手続における特定の個人を識別するための番号の利用等に関する法律」に基づきます。要すれば「為政者である国、地方自治体等」が、「行政の運営効率化(解説:為政者利便ですが、「それは畢竟税負担をする国民の為」と切り返されます)及び公正な給付と負担の確保(解説:米国のSocial Security Number(SSN)と同じく、特に税収ですね)」の為に「個人情報保護法」に関わらず(例外として)、「行政の為のID確認機能を確保」するための法律です。

行政手続における特定の個人を識別するための番号の利用等に関する法律(平成二十五年法律第二十七号)

(目的)
第一条 この法律は、行政機関、地方公共団体その他の行政事務を処理する者が、個人番号及び法人番号の有する特定の個人及び法人その他の団体を識別する機能を活用し、並びに当該機能によって異なる分野に属する情報を照合してこれらが同一の者に係るものであるかどうかを確認することができるものとして整備された情報システムを運用して、効率的な情報の管理及び利用並びに他の行政事務を処理する者との間における迅速な情報の授受を行うことができるようにするとともに、これにより、行政運営の効率化及び行政分野におけるより公正な給付と負担の確保を図り、かつ、これらの者に対し申請、届出その他の手続を行い、又はこれらの者から便益の提供を受ける国民が、手続の簡素化による負担の軽減本人確認の簡易な手段その他の利便性の向上を得られるようにするために必要な事項を定めるほか、個人番号その他の特定個人情報の取扱いが安全かつ適正に行われるよう個人情報の保護に関する法律(平成十五年法律第五十七号)の特例を定めることを目的とする。

 

賢い方法とは「逆に国民にとって不可欠であり、信頼のあるID確認(米国ではSSN)を行政手続きに敷衍する」ことだったのではないでしょうか?より具体的に言うと、

 

(1)従来の組合健保、政府管掌健保の健康保険証を「(様式の統一されたICカードとする)電子保険証」化を先ず進める。(従来の扶養家族も利用できる保険証は廃止して、現在の様に扶養家族も独立した健康保険証を交付する。)

(2)「電子保険証」というICカードには既にID情報が入っているし、過去繰り返し利用されたID確認履歴があるので新規詐欺手法に耐性がある。

(3)その限定的ID情報の認証カードの機能拡大対応(従って統一健保証カードの仕様は総合ID認証カードとして設計)を順次拡大

 

のような流れです。

 

これは「たられば」の話でしかない。しかし、

 

「お余り」の様に出てきた、老人向けの機能限定

 

「顔認証マイナカード」

 

ですが、システム側をスマホ程度の顔認証が可能にグレードアップできれば、

 

「新時代マイナカード」

 

として生まれ変われ、確認証も不要になるかもしれないし、

 

そもそも、「次世代はソフトとしての顔認証ID認証機能」

 

がスマートフォンに入り、カードなんてデバイス(ハードウェア)は消滅する運命にあると思料されるのですが...

 

前々回前回、webに載っていた「AIじゃんけん」というCのプログラムを、解析を兼ねてC++で書いてみました。

 

結局、perceptron関数の腑に落ちる理解には至りませんでしたが、実際に動かしてみて、自分なりに考えてみることにしました。

 

1.まずはサンプルデータから

このプログラムでランダムに戦ってみます。履歴が5回たまらないと「完全体」にはならないので、最初の5回のデータは考えません。

<最初の5回>
1<->2で、あなたの勝ち
2<->2で、引き分け
2<->2で、引き分け
2<->2で、引き分け
2<->1で、マシンの勝ち

 

ここで言えるのは、hand_queデータとweightデータが蓄積されるまでは余りプログラムの手が変化しないように思えることです。

 

2.「完全体」になってからの勝負データとその根拠データ

 

では、次にまた5回戦を戦ってみましょう。

<次の五回>
3<->1で、あなたの勝ち
3<->2で、マシンの勝ち
1<->2で、あなたの勝ち
2<->1で、マシンの勝ち
3<->1で、あなたの勝ち

 

こんな感じで、この対戦の背景となるデータは次のようになっています。

<次の五回のデータ>
(2<->1で、マシンの勝ち)

(解説:マシンが勝ったのでweightが修正されなかったと思ってしまいました。)

expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: 1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
expect[1]に次のweight x hand_que 値を加算: 1, 1, 3, 1, -1, 1, 0, -2, 0, 1, -1, -1, 0, 0, 0, 2
expect[2]に次のweight x hand_que 値を加算: -2, 0, -2, -1, 1, -1, 0, 2, 0, -1, 1, 1, 0, 0, 0, -3
expected[0]の値:-1
expected[1]の値:5
expected[2]の値:-5
3<->1で、あなたの勝ち
予想が外れ、修正された1ブロックのweight: 0, 0, -2, 0, -2, 0, 1, -3, 1, 0, -2, 2, -1, 1, 1, -1
予想が外れ、修正された2ブロックのweight: 1, 1, 1, 0, 2, 0, -1, 3, -1, 0, 2, -2, 1, -1, -1, 2
expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
expect[1]に次のweight x hand_que 値を加算: 0, 0, -2, 0, -2, 0, -1, -3, -1, 0, -2, -2, 1, 1, -1, 1
expect[2]に次のweight x hand_que 値を加算: -1, -1, 1, 0, 2, 0, 1, 3, 1, 0, 2, 2, -1, -1, 1, -2
expected[0]の値:3
expected[1]の値:-11
expected[2]の値:7
3<->2で、マシンの勝ち
予想が外れ、修正された0ブロックのweight: 0, 0, 0, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1
(解説:「ん?」マシンが勝ったのにweightを修正しているぞ。それも一つだけ?)

expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: 0, 0, 0, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
expect[1]に次のweight x hand_que 値を加算: 0, 0, -2, 0, 2, 0, -1, -3, -1, 0, -2, -2, 1, 1, -1, 1
expect[2]に次のweight x hand_que 値を加算: -1, -1, 1, 0, -2, 0, 1, 3, 1, 0, 2, 2, -1, -1, 1, -2
expected[0]の値:-9
expected[1]の値:-7
expected[2]の値:3
1<->2で、あなたの勝ち
予想が外れ、修正された0ブロックのweight: -1, -1, 1, 0, -2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
予想が外れ、修正された2ブロックのweight: 2, 2, 0, 1, 3, -1, 0, 2, 0, 1, 1, -1, 2, -2, 0, 3
expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: -1, 1, -1, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
expect[1]に次のweight x hand_que 値を加算: 0, 0, 2, 0, 2, 0, -1, 3, 1, 0, -2, -2, 1, 1, -1, 1
expect[2]に次のweight x hand_que 値を加算: 2, -2, 0, -1, -3, -1, 0, -2, 0, -1, 1, 1, -2, -2, 0, -3
expected[0]の値:3
expected[1]の値:5
expected[2]の値:-13
2<->1で、マシンの勝ち

予想が外れ、修正された0ブロックのweight: -2, 0, 2, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1
(解説:矢張りマシンが勝ってもweightを修正しています。)

expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: 2, 0, -2, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1
expect[1]に次のweight x hand_que 値を加算: 0, 0, 2, 0, 2, 0, -1, 3, 1, 0, 2, 2, 1, 1, -1, 1
expect[2]に次のweight x hand_que 値を加算: -2, 2, 0, 1, -3, 1, 0, -2, 0, -1, -1, -1, -2, -2, 0, -3
expected[0]の値:-5
expected[1]の値:13(解説:この程度の数に発展してゆきます。)

expected[2]の値:-13(解説:Ditto)

3<->1で、あなたの勝ち

 

3.結果を眺めてみて

最初は(思い込みから)「予想手と現実の手が異なる場合、学習する」ものだと考えていましたが、内部モニターで確認すると「(手のIDである1~3が)同じ手でも、予想手(↑の様に絶対値が大きくなるexpected)と現実の手(-1と+1しかないactual)が異なる場合、(weightを修正して)学習する」ことに変わりはないことが判り、結局

(1)最初から想定していたように「予想手」「現実の手」「重さ」を入力し、「予想手」を出力する、という(パーセプトロンになっている)ことは間違いなさそうですが、

(2)何をどのように評価して、どのように処理するのか、は依然不明

という結論は変わりありませんでした。

 

4.原点に返って考えてみる

「じゃんけん」は典型的なツーサムのゼロサムゲームです。1回の勝負で勝つ確率は3つの手の数それぞれについて相手の手が1/3で、手が3つある為、1/ (3 x 3) x 3 = 1/3で、同じく1/3の確率で発生する引き分けを考慮すると(1/3 + 1/3x1/3 + 1/3x1/3x1/3 ... + (1/3)^n)、勝つ確率は (n=1→∞)Σ(1/3)^nで1/2に収束してゆきます。しかしこれは飽くまで数学的コンテキストの話で、人間的コンテキスト(即ち自分がじゃんけんをする際に何を考えて意思決定しているか)ではそうではなさそうです。では、じゃんけんをする際に何が意思決定(関数)に影響を与える(引数)のでしょうか?

(1)最初は相手の手の予想に関わる情報が無く、根拠のない勘、ゲン担ぎや手の好みになるのでしょうか?

(2)次に明らかに関連する影響要因となるものは「前回(または過去)出した自分の手」「前回(または過去)出した相手の手」「前回(または過去)出した双方の手の結果」になるのでしょう。

(3)更に(引き分けを含み)「相手の次の手を予想する」ことが自分の手の決定に大きな影響を与えることでしょう。

(4)ここまでは良いのですが、これらのデータをどう処理するかで、大昔小中学生辺りで考えたループに嵌ります。

 ①前回自分の(または相手の)出した手を変えてくるか、否か、

 ②変えるとすれば、残りの二手のいずれか。

 ③相手もこちらの出し手を読むので、それに勝つ手を選ぶ筈。

 ④自分と相手はミラーの様に対峙しているので、同じように意思決定するかもしれない。

 ⑤なので、その読みをもうひとつ深読みしなければならない。

  例えば「グー」であいこになった場合、①を検討し、変えてくると想定すると「チョキかパー」で勝つには「グーかチョキ」。変えないと想定すると勝つには「パー」、相手がそう考えるならば、もぅひと読みしてそれに勝つ「チョキ」。更に相手がそう考えるならば、もぅひと読みしてそれに勝つ「グー」... so on and so forth.

(5)ということで、想定を深くして行くほどに3つの選択肢はぐるぐると廻り、堂々巡りに陥ってしまいます。従って、考えられるのは「相手個体(人間)が過去に同様、類似の状況にあった時にどう行動したか」の履歴記録を求め、そのデータに偏差があればそれをどう修正するか、というデータだけから判定するという考え方を取るしかないでしょう。

 

そんなことを考えていたら、このような面白い記事を見つけました。今はやりの機械学習させたらどうなるか、というテーマです。(最後のサンプルはPythonで書かれていますね。)

 

結論(「機械学習でやる意味はない?」)は頷けますし、その場合は相手プレーヤーの個性に応じたデータの蓄積が必要かと思いますが、その場合でも上記4.の問題はあり、結局

 

「たかがじゃんけんでそこまでやる?」

 

という恐ろしい結末が待っていました。そんなことで急激にテンションが下がり、意欲が減退し、この「無駄話」は終わりを迎えるのでした。(最初に「高度に戦略的な」などと書いたことを、自己矛盾を、恥じ入っております。)

 

前回オリジナルのコードを考察し、

 

(1)予測手を取得するperceptron関数を使って、予測手、それに対する勝利手と実際の手の関係から勝敗判定を繰り返すmain関数

と、

(2)過去の手の履歴(デフォルトはN = 5)から、予測と実際の手の際を基に履歴データをグー、チョキ、パー毎に修正履歴を作成する重みデータを乗算して予測手を出す()perceptron関数

 

を眺め、C++に書き直したものが以下です。(解説は詳細に記したコメントを参照して下さい。)

:前回「perceptron関数の理解とANDゲートを使った過去履歴と重み評価の有効性は完全に自信があるわけではありませんが」と書きました。予測手は(修正履歴 x 実際の履歴)の最大値で出しますが、「修正履歴の現実の手の部分 = 現実の手(1)x履歴と、修正履歴の予測の手の部分 = 予測手(-1)x履歴」にしているので、「今回の予測手の前回の現実の手の部分」、「今回の予測手の前回の予測手の部分」共に、前回の現実の手(1)x履歴x履歴と、修正履歴の前回の予測の手の部分 = 予測手(-1)x履歴x履歴になっていることが判ります。しかし、それが持つ意味とその有効性については残念ながら理解できません。

 

【Janken.cpp】

//////////////////////////////////////////////////////////////
// Webで拾ったじゃんけんゲームをC++用に修正
// 原典:https://s-shinomoto.com/janken/c.html
// 1(グー)、2(チョキ)または 3(パー)を入力し、マシンと勝負する。
// 毎回結果(「勝ち、負け、引き分け」)と累積戦績を表示。
// 終了するには0を入力する。
//////////////////////////////////////////////////////////////

#include    <conio.h>    //getch()使用の為
#include    <stdlib>    //srand()、rand()使用の為
#include    <iostream>    //cout、cin使用の為

using namespace std;

//定数定義
#define        Times    5    //過去の手履歴の記録数(5回)

/////////////////////////////////////////////////////////////////
//【perceptron関数】
//過去Times回の履歴(hand_que)を基に、予測手(expected)と現実
//の手(actual)との差異を反映させた、グー、チョキ、パーの順に
//hand_queと同じ履歴が並ぶ「重み(weight)」を作り、それと履歴と
//の積を累計した値を次の予測手(expected)とし、その最大値の番号
//(1(グー)、2(チョキ)、3(パー))を返す。
//
//解説:
//値参照引数hand(m改め)、expected(v改め)はローカル整数配列
//actual(prec改め)と同様、次のような「疑似」「バイナリー表現」と
//なっている。
//    意味  :引数m:配列:配列の内容
//    グー  :   1 :   0:{+1, -1, -1}
//    チョキ:   2 :   1:{-1, +1, -1}
//    パー  :   3 :   2:{-1, -1, +1}
//値参照引数hand_que(x改め)、weight(w改め)、expectedは(mainで使
//わないので)実質「静的変数」と考えてよい。
//hand_queは手のデータ(整数 x 3)のTimes(N改め)回の記録が入り、
//(長さは3 * Tiems + 1)、weight(w改め)はグー(0)、チョキ(1)、
//パー(2)の順に修正された履歴データが入る。(長さ9 * Times + 3)
/////////////////////////////////////////////////////////////////

int perceptron(int hand, int* hand_que, int* weight, int* expected) {

    //ローカル変数(ループ管理用のi、j、kはループ内で宣言する。)
    int pos_max, hand_max, actual[3];
    //引数hand(今回の手)が0の場合、(多くのシステムで)1を返して終了
    if(hand > 0) {
        //引数handの疑似バイナリー表現配列actualを作成
        for(int i = 0; i < 3; i++)
                    actual[i] = -1;
        actual[hand - 1] = +1;
        //予測手と実際の手が相違した場合、weightに
        for(int i = 0; i < 3; i++) {            //actual配列とexpected配列を比較
            if(actual[i] * expected[i] <= 0) {    //(要素のANDを取る)TRUE(1)でなければ
                //前回作成したTimes回の手の履歴データであるhad_queのデータに

//weight_Monitor用(1)
//cout << "予想が外れ、修正された" << i << "ブロックのweight: ";


                for(int j = 0; j < 3 * Times + 1; j++) {
                    //実際の手のデータ(actua)を乗じて履歴データ(weight-注)に加算
                    weight[(3 * Times + 1) * i + j] += actual[i] * hand_que[j];

//weight_Monitor用(2)
//if(j < 3 * Times)
//cout << weight[(3 * Times + 1) * i + j] << ", ";
//else
//cout << weight[(3 * Times + 1) * i + j] << endl;


                }
            }
            //注:weightはグー(0)、チョキ(1)、パー(2)の順で、予想手と実際の手の相違した手
            //のデータについて、実際の手のAND値が加算されて収容される。例えば、
            //例えば、予想手がグー({+1, -1, -1})で、実際の手がチョキ({-1, +1, -1})の
            //場合だと、weightの第1hand_que配列(グー(0)のデータ)について、現実の手(チ
            //ョキ)の第1配列要素の-1と乗算し、
            //hand_queの値が0の場合:0 + 0 x -1 = 0
            //hand_queの値が+1の場合:1 + 1 x -1 = 0
            //hand_queの値が-1の場合:-1 + 0 x -1 = -2
            //と要素の値が修正される。
            //weightの第2履歴配列(チョキ(1)のデータ)については、現実の手(チョキ)の第
            //2配列要素の1と乗算し、
            //hand_queの値が0の場合:0 + 0 x 1 = 0
            //hand_queの値が+1の場合:1 + 1 x 1 = 2 
            //hand_queの値が-1の場合:-1 + -1 x 1 = -2 
            //と要素の値が修正される。なお、予想手が当たった場合、即ち予測手と実際の手の
            //積が1 x 1または-1 x -1の場合はweightへの修正(「学習」)は行われない。

        }
        //手の履歴データの更新(配列上位にある旧い整数 x 3のデータをシフトにより消去し、
        //下位に実際の手データ(整数 x 3)を追加する。

        for(int i = 0; i < 3 * Times - 3; i++)    //「しきいを除くhand_queの長さ、引く3」
            //配列最上位([3 * Times - 1])から順に3つ下位のデータをシフトする。
            hand_que[3 * Times - 1 - i] = hand_que[3 * Times - 4 - i];
        for(int i = 0; i < 3; i++)    //最後に最下位に実際の手のデータを追加する。
            hand_que[i] = actual[i];
        //予測手の更新
        for(int i = 0; i < 3; i++)    //初期値(0)で初期化する。
            expected[i] = 0;

//expected_Monitor用
//cout << "expectを0で初期化完了後、" << endl;


        for(int i = 0; i < 3; i++) {
            //配列expected、weight共にグー(0)、チョキ(1)、パー(2)の順に

//expected_Monitor用(1)
//cout << "expect[" << i << "]に次のweight x hand_que 値を加算: ";


            for(int j = 0; j < 3 * Times + 1; j++) {
                //過去5回の修正された重み(weight)と履歴の積を予想手に順次加算する。
                expected[i] += weight[(3 * Times + 1) * i + j] * hand_que[j];

//expected_Monitor用(2)
//if(j < 3 * Times)
//cout << weight[(3 * Times + 1) * i + j] * hand_que[j] << ", ";
//else
//cout << weight[(3 * Times + 1) * i + j] * hand_que[j] << endl;

            }
        }
        //予め低い値に設定したhand_max(vmax改め)を使って、expectedの最大値をチェックし、
        //その位置をpos_max(kmax改め)に記録する。

        hand_max = -1000000;
        for(int i = 0; i < 3; i++) {
            if(expected[i] >= hand_max) {
                hand_max = expected[i];
                pos_max = i;
            }

//expected_Monitor用(3)
//cout << "expected[" << i << "]の値:" << expected[i] << endl;

        }
    }
    //戻り値はexpectedの最大値であった手(配列添え字+1)

//Monitor用
//cout << "プレーヤーの予想手は" << pos_max + 1 << "となった。" << endl;


    return(pos_max + 1);
}

//////////////////////////////////////////////////////////
//プレーヤーの手(グー(1)、チョキ(2)またはパー(3))の入力
//////////////////////////////////////////////////////////

int get_hand() {

    int hand = -1;
    while(hand > 3 || hand < 0) {
        cout << "あなたの手[グー(1)、チョキ(2)、パー(3)または終了(0)]を入力してください:";
        cin >> hand;
        if(hand > 3 || hand < 0)
            cout << "不正な入力です。1グー(1)、チョキ(2)、パー(3)の何れかを入力してください。\r\n";
    }
    return hand;    //解説:0~3までを返す
}

/////////////////////
//エントリーポイント
/////////////////////

int main() {

    int pred;                    //predは予測手(グー(1)、チョキ(2)、パー(3))
    int expected[3];            //予測手の内部表現(↑参照)
    int hand_que[3 * Times + 1];//Times回分の手の履歴
    int weight[9 * Times + 3];    //パーセプトロンの重み(グー、チョキ、パー毎の修正履歴)
    int game_record[3];            //累計戦績([0] - プレーヤー勝数、[1] - 引き分け数、[2] - PC勝数)
    //初期化
    for(int i = 0; i < 3; i++) {
        expected[i] = 0;
        game_record[i] = 0;
    }
    for(int i = 0; i < 3 * Times; i++)
        hand_que[i] = 0;        //内部状態
    hand_que[3 * Times] = -1;    //しきい
    for(int i = 0; i < 9 * Times + 3; i++)
        weight[i] = 0;            //重み
    //ゲームの開始
    srand((unsigned)time(NULL));    //現在時刻を元に種を生成
    int hand = rand() % 3 + 1;        //プレーヤーの初期手を乱数で決定(解説:単純に'1'では面白くないので。)
    while(hand > 0) {    //グー(1)、チョキ(2)、パー(3)の何れかの場合(オリジナルは「0以下」だが'0'のみにした。)
        //パーセプトロン予測を前もって行う
        pred = perceptron(hand, hand_que, weight, expected);
        //プレーヤーの手の入力
        hand = get_hand();            //'0'は終了
        //プレーヤーとパーセプトロンの「手」を示す
        cout << hand << "<->" << (pred + 1) % 3 + 1;
        //cout << "<->" << (pred + 1) % 3 + 1;
        //(pred + 1) % 3 + 1はパーセプトロンの予測手predに対して勝つ「手」
        //pred = 1(グー)    :(pred + 1) % 3 + 1 = 3(パー)
        //pred = 2(チョキ)    :(pred + 1) % 3 + 1 = 1(グー)
        //pred = 3(パー)    :(pred + 1) % 3 + 1 = 2(チョキ)
        //勝ち負け表示
 
       if(pred == hand){
            cout << "で、マシンの勝ち" << endl;
            game_record[2]++;
        }
        //相手プレイヤーがマシンの予測通りの手を打ったのでマシンの勝ち
        else if((pred % 3) == (hand - 1)) {
            cout << "で、あなたの勝ち" << endl;
            game_record[0]++;
        }
        //相手プレイヤーの勝ち:つまり
        //pred % 3 = 0(予測がパー)だからチョキを出す    :相手プレイヤーはhand - 1 = 0 グー
        //pred % 3 = 1(予測がグー)だからパーを出す        :相手プレイヤーはhand - 1 = 1 チョキ
        //pred % 3 = 2(予測がチョキ)だからグーを出す    :相手プレイヤーはhand - 1 = 2 パー

        else {
            cout << "で、引き分け" << endl;
            game_record[1]++;
        }
        //成績の表示
        cout << "マシン" << game_record[2];
        cout << "勝、引き分け" << game_record[1];
        cout <<"、あなた" << game_record[0];
        cout << "勝\r\n" << endl;
    }
    return 0;
}
 

いかがでしょうか?前よりも分かり易くなったと思います。(プレーヤーの手の入力は外だししました。)

このC++プログラムをコンパイルして走らせると、オリジナルと同様(表示は私好みに変えていますが...)のやり取りで勝負を続け、やめる時は'0'を入力します。

 

が、しかし、

 

weight配列、expectedの作成等、予測手の決定プロセスが見たい、という思いがあり、マジェンダの//(コメント)の所を追加しています。このコメントの二行目の//を外すと、weightがどうなっているのか、それにどういう値が加算されて結果がどうなるのかが分かります。

 

とはいえ、

 

未だに「次の手を予測する原理、その為のどういう評価」の為にこの処理をしているのかはわかりません。(笑;)

 

何れにしても、percepron関数が返す予測手(グー、チョキ、パー)に対する勝利手との対比で勝敗が決まるので、

(1)予測手を確認する

(2)その勝利手を判断する

(3)それに対する勝利手(即ち予測手に対する深度2層の読み)で完全勝利

することが判ると思います。

 

次回は「AI、というか、じゃんけんの必勝アルゴリズムは存在するか?」をご一緒に考え、この無駄話シリーズを終えたいと思います。

 

前回お話ししました通り、以下のCで書かれた「AIじゃんけんプログラム」を、アルゴリズムを理解して、そのままC++で書き直してみましょう。

 

【オリジナル】

#include<stdio.h>

#include<math.h>

 

#define N 5

 

/*1(グー),2(チョキ),3(パー)を入力すると,前もって決めていた

マシンの手を示します.そして「勝ち,負け,引き分け」を表示.

累積度数も示します.0以下の数を入力すると終了.*/

 

int perceptron(int m, int x[], int w[], int v[])

/* 過去のデータx[]にもとづいてw[]をかけた入力v[]の最大値をとる

予想ユニットの番号(1(グー),2(チョキ),3(パー))を返す.*/

{

        int i,j,k,kmax,vmax,prec[3];

        if(m <= 0) goto end;

 

/* 前回の相手プレイヤーの手 m=1,2,3 のバイナリー表現:

グー (m=1):prec={+1,-1,-1}

チョキ(m=2):prec={-1,+1,-1}

パー (m=3):prec={-1,-1,+1} */

        for(k=0;k<3;k++)prec[k] = -1;

        prec[m-1] = +1;

       

/* 各予測ユニットの入力と相手の新しい手のコードの符号が

一致していない場合に誤り訂正学習を行う */

        for(k=0;k<3;k++){

                if(prec[k]*v[k] <= 0){

                        for(j=0;j<3*N+1;j++)  w[(3*N+1)*k+j] += prec[k]*x[j];

                }

        }

       

/* x[0] から x[3*N-1] を3ビット分右に移動 */

                /*for(i=0;i<3*N-4;i++) x[3*N-1-i]=x[3*N-4-i];オリジナル*/

for(i=0;i<3*N-3;i++) x[3*N-1-i]=x[3*N-4-i];/*青木高明氏によりバグ修正2003/03/04 */

               

/* 前回の相手プレイヤーの手{prec[0], prec[1], prec[2]} を

入力スロット最前列{x[0],x[1],x[2]}に挿入 */

                for(i=0;i<3;i++)x[i]=prec[i];

               

/* 予測ユニットへの入力信号の算定 */

        for(k=0;k<3;k++)v[k]=0;

        for(k=0;k<3;k++){for(j=0;j<3*N+1;j++){

                v[k] += w[(3*N+1)*k+j]*x[j];}}

       

/* 最大入力を受けたユニットの番号(から1を引いたもの) */

        vmax=-1000000;

        for(k=0;k<3;k++){

                if(v[k] >= vmax){

                        vmax=v[k];

                        kmax=k;

                        }

        }

 

/* 最大入力を受けた予測ユニットの番号(1,2,3)を返す */

        end:   

        return(kmax+1);

}

 

int main()

{

        char line[100];

        int i,pred,m,v[3],x[3*N+1],w[9*N+3],fw[3];

       

/* 初期化 */

        for(i=0;i<3;i++){

                v[i] = 0;       /* 予測入力 */

                fw[i] = 0;      /* 勝敗累積 */

        }

        for(i=0;i<3*N;i++)x[i] = 0;             /* 内部状態 */

        x[3*N]=-1;                                              /* しきい */

        for(i=0;i<9*N+3;i++)w[i] = 0;   /* 重み */

       

/* ゲームの開始 */

       

        m=1;while(m>0){

 

/* パーセプトロン予測を前もって行う */

                pred=perceptron(m,x,w,v);/* pred は予測手(1,2,3) */

 

/* ここで相手プレイヤーの手をインプット */

                printf("{1(グー),2(チョキ),3(パー)}:");

                fgets(line, sizeof(line), stdin);

                sscanf(line, "%d", &m);

                if(m > 3) m = 3;

                printf("\n%1d",m); /* m はプレイヤーの手*/

 

/* パーセプトロンの「手」を示す */

                printf("<->%1d:   ",(pred+1)%3+1);

/* (pred+1)%3+1はパーセプトロンの予測手predに対して勝つ「手」

pred=1(グー)   :(pred+1)%3+1=3(パー)

pred=2(チョキ) :(pred+1)%3+1=1(グー)

pred=3(パー)   :(pred+1)%3+1=2(チョキ)*/

               

/* これ以降は勝ち負け表示 */

                if(pred==m){printf("[マシンの勝ち]"); fw[2]++;}

/* 相手プレイヤーがマシンの予測通りの手を打ったのでマシンの勝ち*/

                else if((pred%3) == (m-1)){printf("[あなたの勝ち]"); fw[0]++;}

/* 相手プレイヤーの勝ち:つまり

pred%3=0(予測がパー)だからチョキを出す:相手プレイヤーは m-1=0 グー

pred%3=1(予測がグー)だからパーを出す :相手プレイヤーは m-1=1 チョキ

pred%3=2(予測がチョキ)だからグーを出す:相手プレイヤーは m-1=2 パー 

戸田皓治氏によりコメント訂正2003/05/28 */

                else {printf("[ 引き分け ]"); fw[1]++;}

/* 成績の表示 */

                printf("    マシン%4d, 引き分け%4d, あなた%4d \n",fw[2],fw[1],fw[0]);

        }

        return(0);

}

 

【考察】

(1)エントリーポイントの関数であるmain()では、

   ①変数の初期化を行い、

   ②ゲームループに入り、

    ③プレーヤーの手の予測値をperceptron関数で取得し、

    ④プレーヤーに手を入力させ、表示

    ⑤PCの③で取得した予測値に基づく勝利手を表示

    ⑥勝敗判定

   ⑦(プレーヤーが0以下を入力すると)ループを抜け終了

   という流れになっています。詳細はコメントを参照して下さい。

(2)本プログラムの肝である「手の予測」はperceptron関数にあり、

   ①引数mを使って、ローカル変数precに値を代入(初期化)

   -内容はコメント通り。但し「バイナリー表現」というのは「

    通常のコンピューターでいう、0と1のバイナリー」ではなく、

    「+1と-1による疑似バイナリー表現(実際はweightで1以上

    になることもある)」と理解されます。

   ②作成したprec配列と引数のv配列の要素を比較し、同じでな

    い要素があれば、その要素(グー、チョキ、パー)に相応

    するw配列に、prec配列要素とv配列要素の乗算値を加算し

    てゆきます。

   ーこれだけだと何をやっているのか分かりませんね。コメン

    トは「各予測ユニットの入力と相手の新しい手のコードの

    符号が一致していない場合に誤り訂正学習を行う」とあり

    ます。ここで気づくのは整数配列precとv配列は整数3つの

    長さ、整数配列wは整数配列v(3 x N + 1の長さ)の3倍の

    長さであり、+1と-1を使い乗算を行うと

    (例)

     +1 AND +1 = +1

     +1 AND -1 = -1

    結果は+1、-1になることから、この乗算はANDゲートで

    使っているのかもしれない、ということです。

    ③次にx配列(0 ~ 3 x N)は上位に3要素シフトし、空い

     た下位(0~2)にprecの値を追加しています。

    -x配列が、手(m)を疑似バイナリー表現で展開したprec

     配列をN回分記録する為のキュー(que、FIFO型データ

     コレクション)であることが判ります。

    ④次にv配列(これは↓で分かりますが、前回の予測手を

     意味し、実際の手がprecになります)を0で初期化し、

     ②で加工したw配列のグー、チョキ、パー(配列添字0

     ~2)に相当する手の履歴に実際の手の履歴であるx

     の履歴値を乗算(AND)したものを加算してゆきます。

    -↑の②の処理(予測と実際のANDを加算)とこの処理

     修正履歴と履歴のANDの加算の意味を考えてみます。

     wはグー(0)、チョキ(1)、パー(2)の順で、予想手と実

     際の手の相違した手のデータについて、実際の手のAN

     D値が加算されて収容されます。例えば予想手がグー(

     {+1, -1, -1})で、実際の手がチョキ({-1, +1, -1})

     の場合だと、wの第1x配列(グー(0)のデータ)につい

     て、現実の手(チョキ)の第1配列要素の-1と乗算し、

     xの値が0の場合:0 + 0 x -1 = 0

     xの値が+1の場合:1 + 1 x -1 = 0 

     xの値が-1の場合:-1 + 0 x -1 = -2

     と要素の値が修正されます。(誤ったグーのネガティブ

     評価となるのでしょう。)

     wの第2x配列(チョキ(1)のデータ)については、現実

     の手(チョキ)の第2配列要素の1と乗算し、

     xの値が0の場合:0 + 0 x 1 = 0

     xの値が+1の場合:1 + 1 x 1 = 2 

     xの値が-1の場合:-1 + -1 x 1 = -2 

     と要素の値が修正されます。(なお予想手が当たった

     場合はwへの修正(これが「学習」となる)は行われ

     ません。)

     予想手は実際の手の履歴(x)にこの修正履歴を乗算し、

     加算するので、合計値は誤った手の場合低く、当たっ

     た手の場合高くなります。

    ⑤最後に予想手v[3]の各要素の合計値を比較し、最大の

     もの(グー(0)、チョキ(1)、パー(2))を予想手とし

     て返すことになります。

(3)perceptron関数の理解とANDゲートを使った過去履歴と重

   み評価の有効性は完全に自信があるわけではありませんが、

   このように「過去の手の履歴」と「過去の正誤歴」から評

   価関数を用いて予測すること自体は合理的です。

 

では、次回はこの考え方でこのCのプログラムをC++に書き換えてみようと思います。

 

ネタ欠乏症は結構深刻で、未だに「これはっ!」と思うネタに巡り合えていません。

 

しかしっ、

 

「これ、どうかな?」というネタが一つありましたのでご紹介します。それは、

 

じゃんけん。

 

「はぁ、それがネタ?」と思われるでしょうが、「人間が二人、じゃんけんをする場合、そこには高度な戦略的(注)状況が存在する」と思います。

注:Strategic. かのマッキンゼーの日本支社長であった大前研一氏によれば(注の注)、「人の意思決定が他者の意思決定に影響され、またそれが他者の意思決定に影響を与える"interactive"な状況」と記憶しています。

注の注:昔読んだ「企業参謀~戦略的思考とはなにか」に出ていました。

 

要すれば、当事者間の「手の読み合い」ということです。これを情報処理的にどう料理するか?ということは、

(1)統計的アプローチ(出された手の履歴から頻出性等を解析する)、

(2)心理学的アプローチ(プレーヤーの行動特性を出す手の履歴から解析する)、

(3)機械学習的アプローチ(どのような学習方法にするかは別として、発生結果から学習させるアルゴリズムを使う)

等が考えられ、十分にコンピュータープログラミングネタになりえます。

 

と思って、早速関連情報を求めてググってみると、こんなの(↓)

が見つかりました。

 

 

「どれどれ」と中身を覗いてみると、

 

うわっ、きたなっ!(篠本さん、スミマセン、m_(__)_m)

 

と思ってしまうコーディングです。

(1)"#include<math.h>"→このライブラリーは使用されていません。

(2)"#define N 5"→この定数定義は何なのでしょう?

(3)"int perceptron(int m, int x[], int w[], int v[])"→久々に[]を使った配列参照渡しを見ました。また、変数が何を意味しているのか、名称からは全く読み取れません。(コメントもあるのですが、「予測ユニット」等が何かわかりませんし、「3ビット右」と書かれていても、ビット操作はしておらず、単に配列を上位に3つシフトしているだけです。)

(4)"int i,j,k,kmax,vmax,prec[3];"→最初のi、j、kは一般カウンターとして使われており、またkだったりiだったり、一定ではなく、iとjだけでも書けてしまいます。

(5)" if(m <= 0) goto end;"→なかなかC言語で"goto"文を見ることはできません。

etc、etc

 

別にこのプログラムをディスるのが目的ではなく、このような書き方をしていると何をどうしているのかのアルゴリズム理解が非常に難しくなり、私の場合「やる気」が出てきてしまいました。

 

と、いうのは

 

最初の関数、"int perceptron(int m, int x[], int w[], int v[])"のperceptron(パーセプトロン)に引っ掛かり、意味を調べてみるうちに興味をひかれた(注)こともあります。

注:<参考:パーセプトロンについて>
wiki パーセプトロン  (周辺知識もある)
パーセプトロンとは?  (分かり易い)
パーセプトロンの仕組みや用語についての解説  (ちょっと単純か)

 

ニューラルネット、機械学習云々は取り敢えず置いておいて、コードの長さや予測関数一つなので大げさなことはやっていないと思われましたが、

 

あーだ、こーだいうのは先ずは、何をしているのか分かってから

 

ということで先ずは解析の為にC++で書き直してみようと考えています。

 

冗談でなく、本当に燃え尽き、ネタ尽きでウロウロせざるを得ない状態です。(尚、Daggerfallは結局、DOS版のデータをUnity版で承継して、サイズの大きなDOS版はアンインストールしました。())

:前に「Daggerfall Unity版はDOS版が必要」と書きましたが、これは"Yes and No"で、Unity版にDOS版のファイルすべては必要ではなく、DOS版のARENA2フォールダー内の全ファイルとSAVE0-5までのフォールダーとファイルを、Unity版の"...\Daggerfall\DaggerfallUnity_Data\StreamingAssets\GameFiles"にコピーしてやれば、DOS版を消去しても大丈夫でした。

 

ということで、

 

今日もトーシロプログラマーの私ですが、一つシステム開発がらみで書いておこうと思います。そのネタは本日の新聞で「11月までに利用率50%が目標」という、

 

マイナ保険証

 

のことです。

 

私も昔々、情報セキュリティもコンプライアンスも行く勝った時代、米国現地法人の基幹業務データベース(dBaseIVで構築しましたが、何か?)、日本の本社の「課のローカルネットワーク」(まだ他の人はMS-DOSベースの東芝ラップトップで「一太郎」を使っていましたが、何か?-こんなことを勝手にやってもだれも認知していませんでしたね。)、シンガポール現法の業務用データベース等を開発したことがありました()ので、ソフト開発(というか、今の言葉でいえば、業務のDX化)には一家言あります。

:勿論、その後本社の情報セキュリティ基準に引っ掛かり、すべて廃棄または再構築することになりました。

 

それは、

 

1.良いシステムは「安全」、「安定」、「効率」、「効果」的であることは勿論、

2.ユーザーニーズをヒットし、ユーザーフレンドリーで、「思わず使いたくなるようなもの」である必要があり、

3.ユーザーの大多数が導入、利用するまでのロードマップが丁寧に、上手に描けていること。

 

だと思っていました。別の言葉でいえば、↑の1は現在の機械システムの持つ最低限の要求仕様を満たしていること、2は「

システムには機械のみならず、利用する人間の側面(”human factors")が含まれていなければならず」、3は(エントロピー変化云々を言うまでもなく)現在の状態を破壊して新しい状態に移行する為に発生することが有るべき事象を予見、以降の為の道程の立案設定、その制御策が無ければ計画が破綻する、ということです。

 

「マイナ保険証」とは、「行政手続における特定の個人を識別するための番号の利用等に関する法律」に基づく「個人番号(マイナンバー)」を医療保険制度でも活用できるように、という発想で計画され、その利用支援システムが開発されたと理解していますが、昨今の主務官庁大臣発言、国民の行動動向い関わる新聞報道を見ていると、(事実かどうかは別として)次のようなことが起こっているのではないか、という

 

懸念

 

を感じます。

 

(1)「国家の開発したシステムは全国民が利用しなければならない、という誤った開発方針」→これはあり得ないし、実際「資格確認証」を交付することからも明らか。マイナ保険証は「その利用価値を認め、利用する国民が大多数になるようなユーザー利便の高いシステムを構築する」ことにとどめるべきと思われます。

(2)「利用促進のためにシステム価値とは無縁の金銭プロモーション、「昭和パワハラ的」ノルマ主義」→ユーザーが自ずと使いたくなるようなシステムの利便性を向上させることよりも、「ええい、四の五の言わずに使わんかいっ!」という(システムについては矢張り素人の方の、上から目線の)促進策により、却って反発が強まり、「報奨金をもらった後にマイナ保険証を返上」というような「起こるべくして起きた現象」を招いているように思われます。

(3)「国民、医療機関・担当者、健康保険組合等のコアうユーザーの声(反応)から、問題点の分析、解決策の策定、実行と検証・修正という当たり前のPDCA行動が欠如」→システム開発でとられて当たり前の行動がとられずに、(日本人の好きな全体主義的)一億火の玉、マイナ保険証推進!」的強行がとられているのではないかと懸念されてしまいます。

 

思い出すのは、

 

1980年代、未だ16bit MS-DOS機が(当時の)ブラウン管TVほどの大きさだった時代では、民間企業といえども開発された「On-Lineシステム」の利用促進のために従業員という、

 

下々の者に対してトップダウンでに苦行を強制

 

していたことです。(まぁ、当時はコンピューター資源、能力等が制限されていたので仕方がなかったこともありましたが。)しかし、その後インターネットの普及からシステム利用に関わる考え方が変化し、ハードウェア、ソフトウェア共に高度に発達した現在の「ネットワーク時代では、ユーザー利便の高い、且つユーザーフレンドリーなアプリしか生き残れない-会社業績も結果を出せない」という現実に直面して、上述のような開発、導入、定着策に舵を切ったのではないでしょうか?

 

しかし、何も難しい話をしているわけではありません。

 

「旅人(ユーザー)のマントを脱がせる(現状を新しい状態へ変更する)」為に北風と太陽がとった行動の結果

 

は、既にイソップ寓話でながーく語り続けられているのですから。

 

ps. 即ち、問題は極めて"Human_factor"的、社会学的なものであることに気づいてほしいな、ということです。

 

前回、DaggerfallのUnity版もあること、私もDOS版とUnity版の両方をインストールしたことをお話しました。さて、その結果、

 

どーすんだよっ!?

 

という問題が生じ、私のオプションは以下の三つあることが判りました。

 

(1)DOS版とUnity版、いずれかのDaggerfallをアンインストールして、一つで遊ぶ。

(2)DOS版とUnity版、両方で遊ぶ。

(3)DOS版とUnity版、両方ともアンインストールして遊ばない。

 

まぁ、折角(1990年代チック、ノスタルジックで)気に入ったソフトなので(3)は無いでしょう。となると(1)か(2)ですが、「DOS版とUnity版が基本的にゲームの内容や表示オブジェクトは同一」なので、(一番簡単な)(1)が効率的で、(2)は明らかに(余計なストーレージの浪費をしているようなので)冗長的に感じられます。

 

一方、Unity版はさすがに後年ゲームの専門家が作っただけあり、(場面により)ビジュアルが現代的に緻密になるり、DOS版に比べ、キー設定や操作系、保有できる持ち物が多くなる等の「ユーザー目線の改善」があることも確かです。(人によっては単なる「堕落」と映るかもしれませんが。)

 

で、私の選択肢は、

 

ストーレージに余裕があるなら、自分自身で見極めがつくまで、

 

(2)の両方で遊ぶ

 

ことにし、「もういいや」となってから(1)に変更しよう()、ということにしました。

:Unity版が「もういいや」であればUnity版をアンインストールして削除すればよいのですが、DOS版が「もういいや」という場合でもこれはアンインストール、削除してはいけません。Unity版はDOS版に依存しており、単にUIのみを担当していることを思い出してください。

 

しかし、まったく同じようにゲームするのはつまらないので、最終的に私は、

 

【DOS版】

Dark Elf の Battle Mage

【Unity版】

Red Guard の Warrior

 

となって最初のダンジオンの中をさまよっています。(

:外界への出口も見つけて、ほぼ敵も一掃しましたが、まだ隠し扉等があるのかな、と思ってうろついています。

 

さて、偉そうに書いてきた"Elderscrolls : Daggerfall"ですが、ゲームの進捗は"1 / 15,000"(参照)なので、

 

ここで一旦筆を置き、また面白いネタがあれば再度この【Daggerfall】シリーズの続きとして書きたい

 

と思います。では、So long!

 

前回、The Elder Scrolls: Daggerfallというゲームがどのようなものか、簡単に紹介させていただきましたが、このDOSベースのゲームについて調べるうちに、なにやら"Daggerfall Unity"という言葉が混在してくることに気が付きました。

 

なんじゃ、これ?

 

ということで、今度は積極的に"Daggerfall Unity"を調べてみることに。

 

すると、

 

どうもゲームヲタクの有志がこのゲームプログラムを、オリジナルのXnGine Engineではなく、より現代的なUnityをつかって遊べるようにしたもので、ゲームのリソースや構造は全く同じで表示(即ちUI)関係が現在のPC環境に合わせて改善されているもののようです。

 

これで遊ぶには、「Steamから DOS Daggerfall の無料コピー(解説:即ち,Daggerfall Unityで遊ぶには、オリジナルのDOS版のDaggerfallが必要ということです-Microsoft Storeからも入手できます)を、リリースページ(解説:↑のことですが、私はココからダウンロードしました)からDaggerfall Unityの無料コピー を入手できます。その後、最新バージョンのDaggerfall Unityを専用のフォルダーに解凍し、DOS バージョンを指定するだけです。あとはすべて Daggerfall Unity が処理します。」とのことです。詳しくは↓等を参考にしてください。

 

【"Daggerfall Unity"の開発者Interkarmaのインタビュー】

【"Daggerfall Unity"の開発者Gavin Claytonのインタビュー】

 

(同じ記事の別メディアーGamesPark-の記事)

 

 

私もこのブログネタの為にDaggerfall Unityを導入しましたが、手順は以下の通りです。

 

(1)オリジナルのDOS版DaggerfallはMicrosoft Storeで入手。(前回記事参照)

(2)フリーのDaggerfall Unity Ver 1.0をココから入手。

(3)Daggerfall Unityを起動するとセットアッププロセスに入るので、指示通りに進んでください。重要な点は「"ARENA2"フォールダーのあるフォールダーへpathを設定する」ということだけで、これは各自でDOS版Daggerfallの入手、展開先に応じて指定する必要があります。(

:私の場合は前々回書いた通りなので、"C:\XboxGames\The Elder Scrolls- Daggerfall\Content\DF\DAGGER\"を指定しています。

 

そして現在私のPCは、

 

DOS版Daggerfall

 

と、

 

Unity版Daggerfall

 

が動いていますが、これが

 

どーすんだよっ!?

 

という問題となっています。詳しくは次回。