おはやし日記

テーマ……バイク←プログラミング←旅

ncursesでスロットプログラム(絵だけ)

なんとなく思いついたのでスロットを表示するプログラムを書きました。
今はただぐるぐる回るのを表示して止めるだけです。
コードが汚いとか命名がダサいとかは勘弁してください。ノリと勢いで書いたので。
表示には nurses.h を使います。これは,おなじみstdio.hの高等なやつで,画面内でカーソル位置を指定して書き込むことができます。つまり,局所的な画面の更新が可能となるのです。
かなり便利で面白いのに,初心者向けサイトでは一切紹介されないんですよね。まあ扱いがめんどうだからしょうがないけど。

完成品

動かしているところを画面録画して,gifsというサイトでGIFにしました。
gifs.com | Animated Gif Maker and Gif Editor

色が見づらいですね…………

設計思想(そんな大層なものではない)

作りながら思っていたこと,改良していった点

  1. ncursesのmvaddstrを利用して,3箇所にランダムに3つの絵柄を表示しよう
  2. 枠を先に書いておこう。mvaddstrなら上書きしても枠は崩れない
  3. ランダムではなくて,リールを作って回したいな
  4. どうせなら3段表示できるようにしよう
  5. 判定は,投入したコインの枚数に応じて中央列,上下列追加,斜め追加にしよう(これは,昔やったポケモンの何かに出てくるスロットの仕様だった気がするけど違うかもしれない)(まだ判定を実装していない)
  6. それに伴い,一括で書いていたスロットの枠を分離して,どの列まで判定に入るかわかるようにした。副産物として,枠がピカピカする演出が容易になった
  7. リールの表示を細かくした(枠1/2個分進んだ位置の表示をする)
  8. 疲れたのでとりあえず,今までの進捗をブログにまとめようと思った

プログラム

slot.c(メイン)


slot/slot.c

define.h(自作ヘッダファイル)


slot/define.h


ざっくり解説

細かい関数群を別のファイルに書いて結合してもよかったけど,めんどくさいので全部1つのファイルに入れてしまった。定数とか枠の形とかリールの絵柄とかは流石にヘッダファイルに分離した。

mvaddstrでの絵柄表示

一番のミソはこの関数

<slot.c>
void putReel(int winy, int winx, int rn, int num){
    int i;
    for(i=0; i<5; i++){
        mvaddstr(winy+i, winx, illust[reel[rn][num]*5+i]);
    }
}

位置(winy, winx)の位置から,rn番リールのnum番目の絵を表示する。
で,表示するリールの絵柄が書いてあるのが

<define.h>
char illust[5*ILLUSTs][REEL_WIDTH+1] = {
    "777777",
    "77  77",
    "    77",
    "   77 ",
    "   7  ",//0 7
    " 6666 ",
    "66    ",
    "66666 ",
    "66  66",
    " 6666 ",//1 6
    "+----+",
    "|BAR |",
    "|    |",
    "| BAR|",
    "+----+",//2 BAR
    "RRRRR ",
    "R   RR",
    "RRRRR ",
    "R   R ",
    "R    R",//3 RePlay
    "   |  ",
    "  //  ",
    " / \\  ",
    "@@  @@",
    "@@  @@",//4 cherry
    "  ||  ",
    "  ||  ",
    " /  \\ ",
    "/____\\",
    "  @@  ",//5 bell
    "      ",
    "+^^^^+",
    "<OUT!>",
    "+vvvv+",
    "      "//6 OUT
};

さらに,各リールでどの絵柄を使うか指定しているのが

<define.h>
int reel[4][REEL_LONG] = {
    {6, 5, 3, 0, 3, 6, 5, 3, 2, 2, 5, 3, 4, 6, 5, 3, 6, 1, 5, 3, 4},
    {3, 4, 5, 0, 6, 3, 4, 5, 3, 4, 5, 6, 1, 3, 4, 5, 3, 2, 3, 4, 5},
    {3, 5, 0, 2, 3, 5, 6, 4, 3, 5, 2, 1, 4, 3, 5, 6, 4, 3, 5, 6, 4},
    {0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6}
};

さっきの関数では,4番目の変数(文字列)を

illust[ reel[rn][num]*5 + i ]

で呼び出していました。絵柄のi 行目について,配列illustから表示するものを探すわけですが,その位置はreel[rn][num]*5 + iとなってます。例えばrn=1, num=5とすると,reel[1][5]3なので,表示する絵柄の全体像はillust[3 * 5]から5個分表示して

"RRRRR ", <-illust[15]
"R   RR", <-illust[16]
"RRRRR ", <-illust[17]
"R   R ", <-illust[18]
"R    R"  <-illust[19]

となります。
このillust[15 *i]をforループで,mvaddstrの第一変数(書き込みの縦位置)を増やしながら描画します。

3段に描画

前述のputReel関数を使って,三段の枠に表示します。

<slot.c>
void putReel_threeLine(int winy, int winx, int rn, int num){
    putReel(winy+6, winx, rn, num==0?REEL_LONG:num-1);
    /* num-1番のイラストを表示 */
    /* 第4変数 num-1を入れたいがnumが0ならはREEL_LONGにする */
    putReel(winy, winx, rn, num);
    putReel(winy-6, winx, rn, num==REEL_LONG?0:num+1);
    move(0, 0);
    refresh();
}

引数には,真ん中の段での描画位置と,リールの番号と絵柄の番号を入れます。真ん中の段については引数をそのままputReelに渡しますが,上の段と下の段はちょっといじります。
下の段の描画は

putReel(winy+6, winx, rn, num==0?REEL_LONG:num-1);

です。第一変数を+6して描画位置をずらし,第四変数は条件演算子で放り込みます。仮に変数int nを置いて

int n = num==0 ? REEL_LONG : num-1;

とすると,これは

int n;
if(num == 0){
    n = REEL_LONG;
}else{
    n = num - 1;
}

と同義です。今回,絵柄は上から下に流れていくので,下の段には,1つ若い番号の位置にある絵柄を入れたい(真ん中に5番目の絵柄があるとき,下の段には4番目の絵柄が進んでいる)ということになります。よって第四変数にnum -1を入れたいが,C言語では配列の-1番目というのは指定できないので条件分岐で配列の最後に飛ばしています。
ちなみにREEL_LONGはdefine.hの中で定義されたリールの長さです。

上の段についても同様に変数をいじってやれば3段分表示することができます。

半分進んだリールの描画

ちょっと凝ってみました。絵柄が枠の裏に入っている位置の表示です。

<slot.c>
void putReel_halfAfter(int winy, int winx, int rn, int num){
    int i;
    /* num+1番のリールを下2列表示 */
    int numPlusOne = num==REEL_LONG?0:num+1;
    for(i=0; i<2; i++){
        mvaddstr(winy+i, winx, illust[reel[rn][numPlusOne]*5+i+3]);
    }
    /* 1行空行入れて 今の番号のリールを上2列表示 */
    mvaddstr(winy+2, winx, "      ");
    for(i=0; i<2; i++){
        mvaddstr(winy+i+3, winx, illust[reel[rn][num]*5+i]);
    }
}

ここでは,次に表示されるべき絵柄を半分用意する必要があるので,numPlusOneに,条件演算子を使って次の番号を入れています。そのほかは,putReelの変形です。
実際にどう表示されるか見てみます。仮にwiny=0, winx=0, rn = 1, num = REEL_LONGとすると出力する文字列は以下のようになります(ちなみにreel[1][REEL_LONG]5reel[1][0]3)。

"R   R " <- illust[3*5 + 0 +3]
"R    R" <- illust[3*5 + 1 + 3]
"      "
"  ||  " <- illust[5*5 + 0]
"  ||  " <- illust[5*5 + 1]

5番の絵柄(ベル)が半分下に進んで,3番の絵柄(リプレイの"R")が半分降りてきた画になりました。ややこしいけど。

枠の表示

枠は,絵柄を書く前に描画しておいて,その上からリールの絵柄を重ねる感じで使います。あとで枠を書くと空白で上書きしてしまうので。
関数はこれ

<slot.c>
void drawBox(int line){
    int i;
    /* line 1 */
    attrset(COLOR_PAIR( line>0 ? 2 : 5 ));
    for(i=0; i<7; i++){
        mvaddstr(6+i, 0, Box_1[i]);
    }
    /* line 2 */
    attrset(COLOR_PAIR( line>1 ? 3 : 5 ));  /* lineが2か3なら青で,lineが1なら白で */
    for(i=0; i<6; i++){
        mvaddstr(0+i, 0, Box_2up[i]);
    }
    for(i=0; i<6; i++){
        mvaddstr(13+i, 0, Box_2dn[i]);
    }
    /*  line 3*/
    attrset(COLOR_PAIR( line>2 ? 4 : 5 ));
    for(i=0; i<3; i++){
        mvaddstr(0+i, 0, Box_3LU[i]);
        mvaddstr(0+i, 34, Box_3RU[i]);
    }
    for(i=0; i<3; i++){
        mvaddstr(16+i, 0, Box_3LD[i]);
        mvaddstr(16+i, 34, Box_3RD[i]);
    }
    /* line bottun */
    attrset(COLOR_PAIR(5));
    for(i=0; i<3; i++){
        mvaddstr(19+i, 0, Box_Button[i]);
    }
    attrset(COLOR_PAIR(1));/* 戻さないとスロットの色が変になる */
    refresh();
}

引数lineは,色をつけるラインの本数です。冒頭に乗せたGIFをみるとわかるかと思いますが,真ん中の枠を赤,上下の枠を青,斜めのラインを緑で表示できます。
枠を書くときに使う素材(?)は
まず枠の文字列

<define.h>
char Box_2up[6][40] = {
    "      +--------+--------+--------+     ",
    "      |        |        |        |     ",
    "      |        |        |        |     ",
    " [2]--|        |        |        |--[2]",
    "      |        |        |        |     ",
    "      |        |        |        |     "
};
char Box_1[7][40] = {
    "      ============================     ",
    "      #        #        #        #     ",
    "      #        #        #        #     ",
    " [1]--#        #        #        #--[1]",
    "      #        #        #        #     ",
    "      #        #        #        #     ",
    "      ============================     "
};
char Box_2dn[6][40] = {
    "      |        |        |        |     ",
    "      |        |        |        |     ",
    " [2]--|        |        |        |--[2]",
    "      |        |        |        |     ",
    "      |        |        |        |     ",
    "      +--------+--------+--------+     "
};

char Box_Button[3][40] = {
    "         [j^]     [k^]     [l^]        ",
    "                                       ",
    " [q] to quit.    [i] to insert a coin. "
};

char Box_3LU[3][7] = {
    " [3]  ",
    "    \\ ",
    "     \\"
};

char Box_3RU[3][6] = {
    "  [3]",
    " /   ",
    "/    "
};

char Box_3LD[3][7] = {
    "     /",
    "    / ",
    " [3]  "
};

char Box_3RD[3][6] = {
    "\\    ",
    " \\   ",
    "  [3]"
};

枠を,色を付ける部分ごとに分けています。それを拾ってきて,順番に書き出しています。
ここで,文字に色をつけています。

attrset(COLOR_PAIR( i ));

で,i番の色のペアを使うよう指定します(ncurses.hの関数)。その色のペアは

<slot.c>
void colorSetting(){
    init_pair(1, colorYELLOW, colorBLACK);
    init_pair(2, colorRED, colorBLACK);
    init_pair(3, colorBLUE, colorBLACK);
    init_pair(4, colorGREEN, colorBLACK);
    init_pair(5, colorWHITE, colorBLACK);
}

で指定しています。ハードコーディングな感じになっていてビミョーですが……。colorBLACKとかはdefine.hの中で定義していますが,ncurses.hの中でもなんか定義されてたはずです。詳細は不明。

さて,drawBoxの動作は割と簡単で,色を付けるライン数lineに応じて条件演算子で使う色のペアを設定しつつmvaddstrで枠のパーツを入れていくというものです。

リールの回転,停止

リールを回しているのが以下の関数です。一番の山場でした。

<slot.c>
void rotate(int difficulty){
    srand((unsigned int)time(NULL));
    if(difficulty > ILLUSTs)difficulty = ILLUSTs;
    int j = rand()%difficulty;
    int k = rand()%difficulty;
    int l = rand()%difficulty;
    //mvprintw(0, 0, "Start position\t%d\t%d\t%d", j, k, l);
    while(1){
        /* スピン中 現在のイラストと, 1/2段進んだイラストをそれぞれ表示 */
        if(checker[0]==SPIN) putReel_threeLine(1+5+1, 8, 0, j);
        if(checker[1]==SPIN) putReel_threeLine(1+5+1, 8+REEL_WIDTH+3, 1, k);
        if(checker[2]==SPIN) putReel_threeLine(1+5+1, 8+REEL_WIDTH+3+REEL_WIDTH+3, 2, l);
        
        usleep(SPEED);
        
        if(checker[0]==SPIN) putReel_threeAfter(1+5+1, 8, 0, j);
        if(checker[1]==SPIN) putReel_threeAfter(1+5+1, 8+REEL_WIDTH+3, 1, k);
        if(checker[2]==SPIN) putReel_threeAfter(1+5+1, 8+REEL_WIDTH+3+REEL_WIDTH+3, 2, l);
        
        switch(getch()){
            case 'j':
                checker[0] = difficultyGate(difficulty, 0, j);
                putReel_threeLine(1+5+1, 8, 0, checker[0]);
                catchData();
                break;
            case 'k':
                checker[1] = difficultyGate(difficulty, 1, k);
                putReel_threeLine(1+5+1, 8+REEL_WIDTH+3, 1, checker[1]);
                catchData();
                break;
            case 'l':
                checker[2] = difficultyGate(difficulty, 2, l);
                putReel_threeLine(1+5+1, 8+REEL_WIDTH+3+REEL_WIDTH+3, 2, checker[2]);
                catchData();
                break;
            case 'q':goto END;
        }
        if(checker[0]!=SPIN&&checker[1]!=SPIN&&checker[2]!=SPIN){
            resetChecker();
            while(1){
                char next = getch();
                switch(next){
                    case 'q':goto END;break;
                    case 'i':goto NEXT;break;
                }
            }
            NEXT:;
        }
        
        if(checker[0]==SPIN)j++;
        if(checker[1]==SPIN)k++;
        if(checker[2]==SPIN)l++;

        usleep(SPEED);
        if(j==REEL_LONG)j=0; if(k==REEL_LONG)k=0; if(l==REEL_LONG)l=0;
    }
    END:;
}

160行目からのwhile(1)ループでまわしています。
while(1)内は,まずリールが回っている状態であればその描画をする部分があり,そのあとにキーボードからの入力に応じてリールを停止させる部分があり(l. 172~),全リールが止まっていたら別のキー受付処理をする部分があり(l. 190~),最後にリールの位置を示す変数i, j, kを変更しています。
入力処理は,リール回転中は[i][j][k]でそれぞれのリールを止め,[q]で終了。リール停止中は[i]で回転開始,[q]で終了です。
ループから抜けるのにgoto文を使っていますがこれはまあしょうがないでしょう。ループが深すぎました。

今後

とりあえず,当たりの判定とコインの枚数処理的なのを入れたい。そういえば,このスロットは,設定を変えなければ完全にボタンのタイミングに依存する純粋な目押しになります。当てる絵柄を絞る関数は既にあります(difficultyGate)。
あとは,細かいことを別記事で書こうかなと思います。余裕があれば。使ったのに紹介していないncursesの機能がまだあるので。

乗せたコードを丸コピして,-lncursesのリンクをしてコンパイルすると動かせます。
作っている間はMakefileにちょちょっと書いてコンパイルしてましたが……それはまた別の話。

今回は,とりあえずなんか書こうっていうのと,はてな記法でコードを貼っつけてみようっていうのと,そんな感じの動機で書いたのでまとまりもない感じで申し訳ないです。あとは,gifsに出会えたのは良かったです。
ncursesの簡単な使い方は後々書くので(ホント?),プログラミング初心者がncursesに出会って楽しくなるきっかけが出来ればいいなぁと思っています。

参考サイト

プライバシーポリシー ・お問い合わせはこちら