Janken Program Without Conditional Branch

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#define GU 0
#define CHOKI 1
#define PA 2

#define DRAWN 0
#define WON 1
#define LOST 2

const char* hands[3] = {"Gu", "Choki", "Pa"};

int wonCount = 0;
int lostCount = 0;

void IfDrawn(void);
void IfWon(void);
void IfLost(void);
void (*callbacks[3])(void) = {IfDrawn, IfWon, IfLost};

unsigned char GetResult(unsigned char a, unsigned char d)
{
  puts(hands[a]);
  puts(" vs ");
  puts(hands[d]);
  puts("");
  return (unsigned char) ((0x91224 >> ((a << 2 | d) << 1)) & 0x3);
}

unsigned char GetHand(void)
{
  unsigned char v = 0;
  puts( "Input! (0 is GU, 1 is CHOKI, another key is PA)" );
  v = (unsigned char) (_getch() - '0');
  return ((((v >> 1) + 0x7f) >> 7) << 1) | (v & ((((v >> 1) + 0x7f) >> 7) ^ 0x1));
}

void CallProc(void)
{
  callbacks[GetResult(GetHand(), (unsigned char) (rand() % 3))]();
}

void IfDrawn(void)
{
  puts("DRAWN\n");
  CallProc();
}

void IfWon(void)
{
  puts("WON\n");
  wonCount++;
}

void IfLost(void)
{
  puts("LOST\n");
  lostCount++;
}

int main(void)
{
  CallProc();
  CallProc();
  CallProc();
  CallProc();
  CallProc();
  CallProc();
  CallProc();
  CallProc();
  CallProc();
  CallProc();

  printf_s("You won %d time(s) and lost %d time(s).", wonCount, lostCount);
  _getch();

  return 0;
}

注意事項

・変数や関数の名前、および英語はテキトーです。本来、こんな名前の付け方はすべきでありません。
・Windows 以外の環境では検証していません。恐らく _getch と printf_s は置換が必要です。
・これはあくまで実験です。常識的な状況では、どう考えても、普通に条件分岐するほうが賢明です。

解説

呼び出し順に行きます。まず CallProc 関数について。最初に、rand で乱数を取得し、それを 3 で割った余りを計算します。すると 0, 1, 2 の何れかになりますので、それを CPU の手とします。ただし、GetResult が先に呼ばれる場合もあります。C/C++ では、引数の評価順序は定義されていないので、順序に依存したコードは厳禁です。でも今回は、どちらが先でも構わないから、問題ありません。

次に、プレーヤーの手を GetHand 関数で取得します。まず、_getch でキーボードからの入力を待ちます。入力された文字が '0' なら 0 を、'1' なら 1 を、それ以外なら 2 を、GetHand の結果として返します。最後に奇妙な式がありますが、これは、入力された文字を数値 0, 1, 2 に変換するためのものです。

ここまでで、プレーヤーと CPU の手を取得できたので、次に GetResult 関数で勝敗の判定をします。ここにも複雑な式がありますが、これは結果をテーブル(0x91224)から取得するためのものです。他にも方法はあると思うので、まだまだ研究の余地があるはずです。

しかしこう云うビット単位の処理は、アセンブラで書いたほうが早いかも知れませんね。

で、GetResult の戻り値をインデックスとして、callbacks に代入されている該当関数を呼び出します。勝ち(IfWon)と負け(IfLost)の場合、メッセージを表示してカウンタを増やし、処理を戻します。あいこ(IfDrawn)の場合ですが、もう一度 CallProc を呼び出します。勝敗が付くまで繰り返されます。もし延々と勝負が続いた場合、スタックオーバーフローが発生する可能性がありますが、その可能性は無視できるほど小さいので、問題ありません。

以上が 1 勝負ですので、これを 10 回繰り返し、最後に勝敗数を表示して終わりです。普通 for とか使うんでしょうけど、for もまた条件分岐なので、ここでは関数呼び出しそのものを 10 回繰り返すことにしました。