khurata’s blog

khurata’s blog

C による「はさみ将棋」のサンプル

(もともと「Yahoo!知恵袋」の「知恵ノート」だったものを転載しています)
(最終更新日時:2016/5/23)投稿日:2015/11/30

はじめに

C プログラミングで「はさみ将棋」を作ろうとしてるのですがなかなかうまいことできません、という質問があり、それに回答も書いたのですが、なぜか質問ごと取り消されてしまいました(q12152756577)。

f:id:khurata:20190404081421j:plain

 せっかく作った回答例のプログラムを埋もれさせるのももったいないので、「知恵ノート」に載せてみることにしました。

 

プログラム作例

 少し調べてみると、「はさみ将棋」の厳格なルールは、質問の内容だけではカバーし切れていない点も有るようなのですが、本プログラムでは質問文に従って作ることにしました。
http://yahoo.jp/box/SQpwbd
文字エンコーディングShift_JIS です。

 

※ 「Yahoo!ボックス」がいつの間にか共有サービスをやめてしまっていたので、下記にアップロードし直しました(2021/01/09)。

http://khurata.dyndns.dk/QA/q12152756577.zip

 

説明

 詳細についてはプログラムの注釈を見ていただくとして、ここでは本作例について、作者自身がポイントと考えているいくつかの留意点について説明を致します。
(関連知恵ノート「C によるマルバツゲーム実装のサンプル」 https://khurata.hatenablog.com/entry/2019/04/04/080818 の説明と、かなりカブりますが…)

1.関数分割について

 関連知恵ノートや、私の回答でも、何度か書いている事ですが、C は「関数」を組み合わせてプログラムを作っていく言語ですので、「適切な関数分割をすること」が C プログラミングの要諦です。
 やろうと思えば、多くのプログラムを main関数ひとつで書くことが出来ますが、そういうやり方は、「C らしくない」のです。

 作例を見ていただくと、関数はたくさんあるのですけれども、1つ1つの関数は割と短い、と思われるでしょう。 コメントもちょくちょく入れていますし、if や for では律儀にいちいち { と } を使って「ブロック」を構成しているので、実質は、見た目よりも、さらに短いです。

 なぜこんなに短い関数をたくさん作るのかと言うと、「1つの関数は1つの機能を持ち、それにふさわしい名前を付ける」ことを徹底したからです。

 初学者が考え込みながら作る関数は、えてして複雑です。 1つの関数にいくつもの機能を詰め込み、関数は長く、難しいものになり、適切な関数名の付け方にも悩むことになります。
 関数の中で、「いろいろ複雑な処理」が出てきたら、どんどん別の関数に追い出してしまって良いのです。 いやむしろ、そうすべきなのです。 全ての関数において、「これは、もう、これ以上、『他の関数』に追い出すことは出来ない」レベルまで、単純な処理だけを書くべきです。

 そうするとどうなるか……たとえば作例の main関数を見てください。 この main関数には、「はさみ将棋のゲーム進行」しか書かれていません。 「盤を表示する」機能や、「プレイヤーのコマを制御する」機能を別関数に「追い出す」ことによって、main関数はゲーム進行だけを書けば良いようになったのです。
(「プレイヤーを交替する」も、もちろん別関数にして良いのですが、これは C プログラム1行で済む処理なので、main関数にそのまま書き込んでいます)

 「盤を表示する」プログラムや「プレイヤーのコマを制御する」プログラムを、別関数にせず、main関数の中に書くことも出来ます。 しかしそれをやってしまうと、main関数がやりたい事だけ書く、すなわち「はさみ将棋のゲーム進行についてだけを書く」、ということになりません。
 それぞれの関数には、その関数でやりたい事だけを書く……これを徹底すれば、関数の名前もおのずと決まってしまうので、名前を見れば何をする関数かが分かるようになります。 これこそがコツなのです。

2.外部からの入力値について

 プログラム内部で設定したり生成したりする値は、プログラム自身が分かっているので良いのですが、プログラムの外から与えられる値(たとえば人間が入力する値)は、要注意です。

 本作例では、人間からの入力を受け付けるのは inputPosition関数(195~219行目)だけですが、この関数では、「妥当な入力で無ければ通さない」ことを徹底しています。 204~214行目の while ループを抜けるには、人間が妥当な入力をするしかありません。
 妥当な入力値が与えられなければ、inputPosition関数を抜けることは無く、従って、inputPosition関数の呼び元に戻ることもありません。 controlMarkOf関数(154~191行目)にある2つの while 文も、同様の目的で書かれています。
 このように、自関数の外部からの入力値を、できるだけ入力処理に近いところでチェックし「せきとめる」ことによって、他の関数がラクに・正しい処理が出来るようになります。

3.長くなってしまう関数の分割について

 たとえ1機能であっても、行数が長くなってしまう関数、というものがままあります。 本作例では、takeAround関数(317~333行目)が、最初はまさにそうでした。 今は、takeN、takeE、takeS、takeW という4関数に分割・委譲して短くなっていますが、当初、これらは全て takeAround関数に書いていました。
 takeAround関数は「コマの周辺状況を操作する」関数であり、これはこれで1機能だからです。

 しかし、いかに1関数1機能で・命名が妥当であっても、「行数が長い」、ということは、それだけで関数分割の理由に成り得ます。 なぜなら、我々人間は、1画面の中に収まっている範囲の事なら、注意して見ることが出来るからです。
 つまり人間にとって、1画面に収まらず、何度かスクロールして見なければならないような関数は、たとえ内容が簡単であっても、先頭の方と、終結の方とを突き合わせて見たり考えたりしづらいのです。 ひと目で見渡せる範囲に「全て」を収めることは、誤りを見つけやすくするテクニックなのです。

4.その他

 たとえば board を独自の型として宣言する、とか、マクロ定数を使う是非であるとかについては、関連知恵ノート「C によるマルバツゲーム実装のサンプル」 https://khurata.hatenablog.com/entry/2019/04/04/080818 と同様のことが言えます。

 

おわりに

 もちろん、この作例だけが絶対に正しい唯一の実装ではなく、様々な改良・改造の余地があり、あるいは全く別のアプローチもありますが……ともあれ、関連知恵ノートと、本知恵ノートの、2つの作例を見れば、大抵のボードゲームやカードゲームは C で書けそうだ、と思えるのではないでしょうか。 本作例が、何かの参考になれば幸いです。
(転載以上)