2010年9月12日日曜日

スネークゲーム上げてみた。

前回のソースを多少遊びやすくした程度のものを上げてみます。
ヒトバシラーな覚悟のある方はダウンロードしてみてね。
ダウンロード

画面イメージ

2010年9月11日土曜日

スネークゲームの雛形

DXライブラリでスネークゲームの雛形を作ってみました。
ぬるっと動かすことにこだわったら、こんな長くなっちゃった。
#include "dxlib.h"

// ウィンドウの大きさ
#define WINDOW_WIDTH  640
#define WINDOW_HEIGHT  480

// DXLIB関数の返り値をわかりやすく。
#define DX_FALSE   (-1)
#define DX_TRUE    0

// キャラクタの大きさとフィールドの大きさを決める。
#define CHAR_SIZE   16
#define FIELD_WIDTH   (WINDOW_WIDTH / CHAR_SIZE)
#define FIELD_HEIGHT  (WINDOW_HEIGHT / CHAR_SIZE)
#define FIELD_ARRAY   (FIELD_WIDTH * FIELD_HEIGHT)

// へびのパラメータ
#define SNAKE_LENGTH  5
#define SNAKE_MAX   256
#define SNAKE_SPEED   2
#define FOOD_NUM   100
#define ENEMY_NUM   30

// X,Y座標から一次元配列のインデックスを算出するマクロ
#define GET_INDEX(A,B)  ((A) + (B) * FIELD_WIDTH)

// キャラクタコードの列挙体
enum eCharCode{ CHR_BLANK,CHR_FOOD,CHR_ENEMY,CHR_SNAKEH,CHR_SNAKEB,CHR_END };

// キーコードの列挙体
enum eKeyCode{ KEY_UP,KEY_DOWN,KEY_RIGHT,KEY_LEFT,KEY_ESC };

// X,Y座標の増分を設定(4方向)
const int Vx[] = { 0, 0, 1,-1};
const int Vy[] = {-1, 1, 0, 0};

// へびのキャラクタパラメータを格納する構造体
typedef struct s_snake{
 int x;
 int y;
 int vx;
 int vy;
}SNAKEBODY;

// ゲーム全体のパラメータを格納する構造体
typedef struct s_global{
 SNAKEBODY sb[SNAKE_MAX];
 int score;
 int snake_length;
 int stage[FIELD_ARRAY];
 int color[CHR_END];
 int key_code;
}GLOBALS;

// 指定座標のキャラクタコードを取得
int GetChar(GLOBALS *g,int x,int y){
 return g->stage[GET_INDEX(x / CHAR_SIZE,y / CHAR_SIZE)];
}

// 指定座標に指定キャラクタを配置
void SetChar(GLOBALS *g,int x,int y,int c){
 g->stage[GET_INDEX(x / CHAR_SIZE,y / CHAR_SIZE)] = c;
}

// ゲームパラメータ初期化処理
void GameInit(GLOBALS *g){
 int i;
 //乱数初期化
 SRand(GetNowCount());
 //ステージ配列をブランクで埋める
 for(i=0;i<FIELD_ARRAY;i++) g->stage[i] = CHR_BLANK;
 //へび配列を初期化し、その情報を元にへびのキャラクタをステージ配列に配置する
 for(i=0;i<SNAKE_MAX;i++){
  if(i < SNAKE_LENGTH){
   const int x = (FIELD_WIDTH / 2) * CHAR_SIZE;
   const int y = (FIELD_HEIGHT / 2 + i) * CHAR_SIZE;
   g->sb[i].x = x;
   g->sb[i].y = y;
   g->sb[i].vx = 0;
   g->sb[i].vy = -1;
   if(i) SetChar(g,x,y,CHR_SNAKEB);
  }else{
   g->sb[i].x = 0;
   g->sb[i].y = 0;
   g->sb[i].vx = 0;
   g->sb[i].vy = 0;
  }
 }
 //色の設定(超適当)
 for(i=0;i<CHR_END;i++){
  g->color[i] = GetColor(
   255 - ((i % 2) * 200),
   255 - ((i % 3) * 100),
   255 - ((i % 5) * 50)
  );
 }
 //障害物を置く
 for(i=0;i<ENEMY_NUM;i++){
  int r = GetRand(FIELD_ARRAY - 1);
  if(g->stage[r] == CHR_BLANK) g->stage[r] = CHR_ENEMY;
  else i--;
 }
 //エサを置く
 for(i=0;i<FOOD_NUM;i++){
  int r = GetRand(FIELD_ARRAY - 1);
  if(g->stage[r] == CHR_BLANK) g->stage[r] = CHR_FOOD;
  else i--;
 }
 g->score = 0;
 g->snake_length = SNAKE_LENGTH;
 g->key_code = KEY_UP;
}

// へびの長さを伸ばす
void AddLength(GLOBALS *g){
 int l = g->snake_length;
 if(l == SNAKE_MAX) return;
 g->sb[l].x = g->sb[l - 1].x - g->sb[l - 1].vx * CHAR_SIZE; 
 g->sb[l].y = g->sb[l - 1].y - g->sb[l - 1].vy * CHAR_SIZE;
 g->sb[l].vx = g->sb[l - 1].vx; 
 g->sb[l].vy = g->sb[l - 1].vy; 
 g->snake_length++;
}

// 当たり判定処理
int Collision(GLOBALS *g){
 int *stg = g->stage;
 SNAKEBODY *s = g->sb;
 const int chr = GetChar(g,s[0].x,s[0].y);
 if(chr == CHR_FOOD) AddLength(g);
 else if((chr == CHR_ENEMY)||(chr == CHR_SNAKEB)) return DX_FALSE;
 return DX_TRUE;
}

// へびを動かす
int MoveSnake(GLOBALS *g){
 SNAKEBODY *s = g->sb;
 int i,l = g->snake_length - 1;
 //方向を決める
 for(i=(l-1);i>=0;i--){
  if((s[i].x % CHAR_SIZE == 0)&&(s[i].y % CHAR_SIZE == 0)){
   if((i + 1) == l) SetChar(g,s[l].x,s[l].y,CHR_BLANK);
   s[i + 1].vx = s[i].vx;
   s[i + 1].vy = s[i].vy;
   if(i == 0){
    if(g->key_code >= 0){
     if(((s[i].vx == 0)&&(g->key_code > KEY_DOWN))||
      ((s[i].vy == 0)&&(g->key_code < KEY_RIGHT))){
      s[i].vx = Vx[g->key_code];
      s[i].vy = Vy[g->key_code];
     }
    }
    if(Collision(g) == DX_FALSE) return DX_FALSE;
   }else SetChar(g,s[i].x,s[i].y,CHR_SNAKEB);
  }
 }
 //方向に沿って移動させる
 for(i=0;i<g->snake_length;i++){
  s[i].x += s[i].vx * SNAKE_SPEED;
  s[i].y += s[i].vy * SNAKE_SPEED;
  if(i == 0){
   if((s[i].x < 0)||
    (s[i].x > (WINDOW_WIDTH - CHAR_SIZE))||
    (s[i].y < 0)||(s[i].y > (WINDOW_HEIGHT - CHAR_SIZE)))
     return DX_FALSE;
  }
 }
 return DX_TRUE;
}

// キーコードを取得する
int GetKey(void){
 switch(GetJoypadInputState(DX_INPUT_KEY_PAD1)){
 case PAD_INPUT_DOWN:
  return KEY_DOWN;
 case PAD_INPUT_UP:
  return KEY_UP;
 case PAD_INPUT_RIGHT:
  return KEY_RIGHT;
 case PAD_INPUT_LEFT:
  return KEY_LEFT;
 case PAD_INPUT_START:
  return KEY_ESC;
 }
 return -1;
}

// ゲーム更新処理
int GameUpdate(GLOBALS *g){
 int k = GetKey();
 if(k >= 0){
  g->key_code = k;
  if(g->key_code == KEY_ESC) return DX_FALSE;
 }
 if(MoveSnake(g) == DX_FALSE) return DX_FALSE;
 return DX_TRUE; 
}

// ゲーム描画処理
void GameDraw(GLOBALS *g){
 int x,y,i;
 //背景描画
 //DrawBox(0,0,WINDOW_WIDTH,WINDOW_HEIGHT,0,TRUE);
 for(y=0;y<FIELD_HEIGHT;y++){
  const int yy = y * CHAR_SIZE;
  for(x=0;x<FIELD_WIDTH;x++){
   const int xx = x * CHAR_SIZE;
   const int col = g->color[GetChar(g,xx,yy)];
   DrawBox(xx,yy,xx + CHAR_SIZE,yy + CHAR_SIZE,col,TRUE);
  }
 }
 //へびの描画
 for(i=(g->snake_length - 1);i>= 0;i--){
  const int x = g->sb[i].x;
  const int y = g->sb[i].y;
  int col;
  if(i) col = g->color[CHR_SNAKEB];
  else col = g->color[CHR_SNAKEH];
  // 実際は4キャラ分しか描画してないんだぜ。
  if((i < 2)||(i >= g->snake_length - 2)){
   DrawBox(x,y,x + CHAR_SIZE,y + CHAR_SIZE,col,TRUE);
  }
 }
}

// Windowsアプリケーションエントリポイント
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
                               LPSTR lpCmdLine,int nCmdShow){
 GLOBALS g;
 ChangeWindowMode(TRUE);
 if(DxLib_Init() == DX_FALSE) return DX_FALSE;
 SetDrawScreen(DX_SCREEN_BACK);
 SetWaitVSyncFlag(TRUE);
 GameInit(&g);
 while(ProcessMessage() == DX_TRUE){
  if(GameUpdate(&g) == DX_FALSE) break;
  GameDraw(&g);
  ScreenFlip();
 }
 DxLib_End();
 return 0;
}

2010年9月5日日曜日

マインスイーパ移植したお!(Windows版)

Linux(Ubuntu)版をWindows版に移植しました。
っても、入出力まわりをちょこっと変えただけですが。
GUI版はすでにWindows標準のがあるので作る気はありません。
ビットマップの素材用意するのも結構大変だしねw
Windowsのコマンドプロンプト上だとエスケープシーケンスが使えないので、
Win32APIを使ってそこら辺を操作しています。
画面消去もコマンドプロンプト標準のclsコマンドを投げてるだけです。
何という手抜きw
// MineSweeper for Windows CUI
// Author:Imoimo
// Maked At:2010/08/29

// 各種インクルード
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <conio.h>   // DOS環境依存
#include <windows.h>  // Windows環境依存

// 定数定義
// ゲームパラメータ
#define STAGE_X     15
#define STAGE_Y     10
#define SCREEN_X    (STAGE_X + 2)
#define SCREEN_Y    (STAGE_Y + 2)
#define STAGE_ARRAY   (STAGE_X * STAGE_Y)
#define BOMB_NUM    20
#define GOVER_TIME   9999
#define MAX_STR_BUF   256
#define POLL_DELAY   16
#define SECOND_RACIO  500.0
// キーコード
#define KEY_NUM     8
#define KEY_SPACE    0x20
#define KEY_ENTER    0x0D
#define KEY_M     'Z'
#define KEY_UP     0x48
#define KEY_DOWN    0x50
#define KEY_LEFT    0x4B
#define KEY_RIGHT    0x4D
#define KEY_E     'E'
// 表示色設定
#define COL_BLACK  0
#define COL_WHITE  15
// 各種表示文字列
#define TITLE_STR    "-MineSweeper-"
#define HELP_STR    "[HELP]Arrow=Move,Z=Mark,Space=Flag,Enter=Open,E=Exit."
#define GOVER_STR    "Game Over!"
#define CLEAR_STR    "Game Clear!"
#define EXIT_STR    "Exit Game."

// キャラクタコードの列挙体
enum eCharCode{
 CHR_OPEN,
 CHR_1,
 CHR_2,
 CHR_3,
 CHR_4,
 CHR_5,
 CHR_6,
 CHR_7,
 CHR_8,
 CHR_FLAG,
 CHR_MARK,
 CHR_BOMB,
 CHR_CLOSE,
 CHR_CORNER,
 CHR_H_BORDER,
 CHR_V_BORDER,
};

// ゲームループ切り替えスイッチの列挙体
enum eGameMode{
 GAME_INIT,
 GAME_CLEAR,
 GAME_BODY,
 GAME_OVER,
 GAME_EXIT
};

// フラグと爆弾の一致判定をするかどうか
enum eFlagMode{ FLAG_TRUE,FLAG_FALSE };

// 表示キャラクタの配列
const char Chars[] = {
 ' ',
 '1','2','3','4','5','6','7','8',
 'P','X','*','#',
 '+','-','|'
};

// 表示顔文字の配列
const char *Faces[] = {"(--)","(^^)","(@@)","(xx)"};

// Windows環境依存・Win32API用
HANDLE hStdOut;

// ゲームパラメータ保存用グローバル変数
int Stage[STAGE_ARRAY];  //ステージの状態を保持
int Bombs[BOMB_NUM];  //爆弾の位置を保持
int X;      //カーソルの横座標
int Y;      //カーソルの縦座標
int TimeStart;    //ゲーム開始時のtime関数の値
int TimeCount;    //ゲーム開始からの経過秒数
int HitBombs;    //正解数のカウンタ
int FlagNum;    //旗の残り数
int GameCount;    //手数


// Windows環境依存・Win32API_Start

void setStdOutHandle(){
 hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
}

void releaseStdOutHandle(){
 CloseHandle(hStdOut);
}

void setReverse(){
 SetConsoleTextAttribute(hStdOut, COL_BLACK + (COL_WHITE << 4));
}

void setNormal(){
 SetConsoleTextAttribute(hStdOut, COL_WHITE + (COL_BLACK << 4));
}

// Windows環境依存・Win32API_End


// DOS環境依存・入出力処理_Start

// 画面消去
void cls(void){
 system("cls");
}

// getchラッパー関数(一定時間でループを抜ける。)
char getKey(void){
 char c = 0;
 int sec = 1 / (POLL_DELAY / SECOND_RACIO);
 while(sec-- > 0){
  Sleep(POLL_DELAY);
  if(kbhit()){
   c = getch();
   break;
  }
 }
 return c;
}

// DOS環境依存・キーボード入力処理_End


// 指定範囲(min〜max)の乱数値を返す。
int getRandom(int min,int max){
 return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}

// 2次元座標から一次元配列のインデックスを返す
int getIndex(int x,int y){
 return x + y * STAGE_X;
}

// 配列インデックス値から2次元座標の横位置を返す
int getX(int index){
 return index % STAGE_X;
}

// 配列インデックス値から2次元座標の縦位置を返す
int getY(int index){
 return index / STAGE_X;
}

// ステージ初期化
void initStage(void){
 int i;
 for(i=0;i<STAGE_ARRAY;i++) Stage[i] = CHR_CLOSE;
 for(i=0;i<BOMB_NUM;i++) Bombs[i] = 0;
}

// 爆弾配置
void setBomb(){
 int i,j;
 srand(time(NULL));
 for(i=0;i<BOMB_NUM;i++){
  int bi,ind = getIndex(X,Y),dup = 0;
  bi = getRandom(0,STAGE_ARRAY - 1);
  if(bi == ind){
   dup = 1;
  }else{
   for(j=0;j<i;j++){
    if(Bombs[j] == bi) dup = 1;
   }
  }
  if(dup == 0) Bombs[i] = bi;
  else i--;
 }
}

// ゲームパラメータ初期化
int initGame(void){
 X = STAGE_X / 2;
 Y = STAGE_Y / 2;
 TimeStart = time(NULL);
 TimeCount = 0;
 HitBombs = 0;
 FlagNum = BOMB_NUM;
 GameCount = 0;
 initStage();
 return GAME_BODY;
}

// ゲームフィールドの横幅中央に文字列を表示する。
void drawCenter(const char *c){
 int len = strlen(c);
 int i,pad = (SCREEN_X - len) / 2;
 char cPads[SCREEN_X],cBuf[MAX_STR_BUF];
 strcpy(cBuf,c);
 for(i=0;i<SCREEN_X;i++) cPads[i] = ' ';
 if((pad + len) > SCREEN_X){
  cBuf[SCREEN_X - pad] = '¥0';
 }
 strcpy(cPads + pad,cBuf);
 puts(cPads);
}

// 枠の表示
void drawBorder(void){
 int i;
 for(i=0;i<SCREEN_X;i++){
  if((i == 0)||(i == STAGE_X + 1)) putchar(Chars[CHR_CORNER]);
  else putchar(Chars[CHR_H_BORDER]);
 }
 putchar('¥n');
}

// ステージの表示
void drawStage(void){
 int x,y;
 for(y=0;y<STAGE_Y;y++){
  putchar(Chars[CHR_V_BORDER]);
  for(x=0;x<STAGE_X;x++){
   if((x == X)&&(y == Y)) setReverse();
   putchar(Chars[Stage[getIndex(x,y)]]);
   setNormal();
  }
  putchar(Chars[CHR_V_BORDER]);
  putchar('¥n');
 }
}

// 画面全体の表示
void drawScreen(int face){
 cls();
 drawCenter(TITLE_STR);
 drawCenter(Faces[face]);
 drawBorder();
 drawStage();
 drawBorder();
 printf("Time : %04d Secs¥n",TimeCount);
 printf("Count: %04d Times¥n",GameCount);
 printf("Flag : %03d/%03d¥n",FlagNum,BOMB_NUM);
 puts(HELP_STR);
}

// ヒントの取得
int getHint(int x,int y,int sw){
 int i,count = CHR_OPEN;
 for(i=0;i<BOMB_NUM;i++){
  const int bx = getX(Bombs[i]);
  const int by = getY(Bombs[i]);
  if(Bombs[i] == getIndex(x,y)) return CHR_BOMB;
  else if((bx >= (x - 1))&&(bx <= (x + 1))
     &&(by >= (y - 1))&&(by <= (y + 1))){
   if((sw)||(Stage[getIndex(bx,by)] != CHR_FLAG)) count++;
  }
 }
 return count;
}

// ヒントのないセルを自動的に開く
void autoOpen(const int x,const int y){
 int i;
 const int vx[] = { 0, 0,-1, 1,-1,-1, 1, 1};
 const int vy[] = {-1, 1, 0, 0,-1, 1,-1, 1};
 for(i=0;i<8;i++){
  int *stg;
  const int xx = x + vx[i];
  const int yy = y + vy[i];
  if((xx >= 0)&&(xx < STAGE_X)&&(yy >= 0)&&(yy < STAGE_Y)){
   stg = &Stage[getIndex(xx,yy)];
   if(*stg == CHR_CLOSE){
    *stg = getHint(xx,yy,FLAG_FALSE);
    if(*stg == CHR_OPEN) autoOpen(xx,yy);
   }
  }
 }
}

// ヒントを中心とした8マスにマークされていない爆弾がなければ周囲の8マスを開く
void nearOpen(){
 int *stg = &Stage[getIndex(X,Y)];
 if((*stg == CHR_OPEN)||(*stg > CHR_8)) return;
 if(getHint(X,Y,FLAG_TRUE) == 0) autoOpen(X,Y);
}

// 旗を置く
int putFlag(void){
 int *stg = &Stage[getIndex(X,Y)];
 if((*stg == CHR_CLOSE)&&(FlagNum > 0)){
  *stg = CHR_FLAG;
  FlagNum--;
  if(getHint(X,Y,FLAG_FALSE) == CHR_BOMB){
   HitBombs++;
   if(HitBombs >= BOMB_NUM) return GAME_CLEAR;
  }
 }else if(*stg == CHR_FLAG){
  *stg = CHR_CLOSE;
  FlagNum++;
  if(getHint(X,Y,FLAG_FALSE) == CHR_BOMB) HitBombs--;
 }
 return GAME_BODY;
}

// 目印を置く
int putMark(void){
 int *stg = &Stage[getIndex(X,Y)];
 if(*stg == CHR_CLOSE) *stg = CHR_MARK;
 else if(*stg == CHR_MARK) *stg = CHR_CLOSE;
 return GAME_BODY;
}

// 隠されているセルを開く
int openCell(void){
 int *stg = &Stage[getIndex(X,Y)];
 if(*stg == CHR_CLOSE){
  if(GameCount++ == 0) setBomb();
  *stg = getHint(X,Y,FLAG_FALSE);
  if(*stg == CHR_OPEN) autoOpen(X,Y);
  else if(*stg == CHR_BOMB) return GAME_OVER;
 }else{
  nearOpen();
 }
 return GAME_BODY;
}

// カーソルを上に移動
int moveUp(void){
 if(Y > 0) Y--;
 return GAME_BODY;
}

// カーソルを下に移動
int moveDown(void){
 if(Y < (STAGE_Y - 1)) Y++;
 return GAME_BODY;
}

// カーソルを左に移動
int moveLeft(void){
 if(X > 0) X--;
 return GAME_BODY;
}

// カーソルを右に移動
int moveRight(void){
 if(X < (STAGE_X - 1)) X++;
 return GAME_BODY;
}

// ゲームを終了する。
int exitGame(void){
 puts(EXIT_STR);
 return GAME_EXIT;
}

// ゲーム更新処理
int updateGame(void){
 char c = getKey();
 const char keyCode[] = {
  KEY_SPACE,
  KEY_ENTER,
  KEY_M,
  KEY_UP,
  KEY_DOWN,
  KEY_LEFT,
  KEY_RIGHT,
  KEY_E
 };
 int (*f[])(void) ={
  putFlag,
  openCell,
  putMark,
  moveUp,
  moveDown,
  moveLeft,
  moveRight,
  exitGame
 };
 int i;
 TimeCount = time(NULL) - TimeStart;
 if(TimeCount >= GOVER_TIME) return GAME_OVER;
 for(i=0;i<KEY_NUM;i++){
  if(toupper(c) == keyCode[i]) return f[i]();
 }
 return GAME_BODY;
}

// ゲームメインループ
int gameBody(void){
 drawScreen(GAME_BODY);
 return updateGame();
}

// ゲームクリア時の処理
int gameClear(void){
 drawScreen(GAME_CLEAR);
 puts(CLEAR_STR);
 getch();
 return GAME_EXIT;
}

// 全部の爆弾を表示する
void displayAllBombs(){
 int i;
 for(i=0;i<BOMB_NUM;i++) Stage[Bombs[i]] = CHR_BOMB;
}

// ゲームオーバー時の処理
int gameOver(void){
 displayAllBombs();
 drawScreen(GAME_OVER);
 puts(GOVER_STR);
 getch();
 return GAME_EXIT;
}

// メイン関数(エントリポイント)
int main(void){
 int gm = GAME_INIT;
 int (*f[])(void) = {initGame,gameClear,gameBody,gameOver};
 setStdOutHandle();
 while(gm != GAME_EXIT) gm = f[gm]();
 releaseStdOutHandle();
 return 0;

}
実行イメージ

2010年9月2日木曜日

マインスイーパできたお!(Linux版)

とりあえずゲームとしての体裁が出来上がりました。
コーディングの際アドバイスをくれた職場の人に感謝!
// MineSweeper
// Author:Imoimo
// Maked At:2010/08/29

// 各種インクルード
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <termios.h> //Linux環境依存
#include <unistd.h>  //Linux環境依存

// 定数定義
// ゲームパラメータ
#define STAGE_X   15
#define STAGE_Y   10
#define SCREEN_X  (STAGE_X + 2)
#define SCREEN_Y  (STAGE_Y + 2)
#define STAGE_ARRAY  (STAGE_X * STAGE_Y)
#define BOMB_NUM  20
#define GOVER_TIME  9999
#define MAX_STR_BUF  256
#define POLL_DELAY  16667
#define SECOND_RACIO 100000.0
// キーコード
#define KEY_NUM   8
#define KEY_SPACE  0x20
#define KEY_ENTER  0x0A
#define KEY_M   'M'
#define KEY_UP   0x41
#define KEY_DOWN  0x42
#define KEY_LEFT  0x44
#define KEY_RIGHT  0x43
#define KEY_E   'E'
// エスケープシーケンスコード
#define ESC_CLEAR  "\x1b[2J\x1b[0;0H"
#define ESC_NORMAL  "\x1b[0m"
#define ESC_REVERSE  "\x1b[7m"
// 各種表示文字列
#define TITLE_STR  "-MineSweeper-"
#define HELP_STR  "[HELP]Arrow=Move,M=Mark,Space=Flag,Enter=Open,E=Exit."
#define GOVER_STR  "Game Over!"
#define CLEAR_STR  "Game Clear!"
#define EXIT_STR  "Exit Game."

// キャラクタコードの列挙体
enum eCharCode{
 CHR_OPEN,
 CHR_1,
 CHR_2,
 CHR_3,
 CHR_4,
 CHR_5,
 CHR_6,
 CHR_7,
 CHR_8,
 CHR_FLAG,
 CHR_MARK,
 CHR_BOMB,
 CHR_CLOSE,
 CHR_CORNER,
 CHR_H_BORDER,
 CHR_V_BORDER,
};

// ゲームループ切り替えスイッチの列挙体
enum eGameMode{
 GAME_INIT,
 GAME_CLEAR,
 GAME_BODY,
 GAME_OVER,
 GAME_EXIT
};

// フラグと爆弾の一致判定をするかどうか
enum eFlagMode{ FLAG_TRUE,FLAG_FALSE };

// Linux環境依存・keybd()、getch()関数の代替処理用
// デフォルトのターミナルパラメータを保存する構造体
static struct termios TOrigin;

// 表示キャラクタの配列
const char Chars[] = {
 ' ',
 '1','2','3','4','5','6','7','8',
 'P','X','*','#',
 '+','-','|'
};

// 表示顔文字の配列
const char *Faces[] = {"(--)","(^^)","(@@)","(xx)"};

// ゲームパラメータ保存用グローバル変数
int Stage[STAGE_ARRAY];  //ステージの状態を保持
int Bombs[BOMB_NUM];  //爆弾の位置を保持
int X;      //カーソルの横座標
int Y;      //カーソルの縦座標
int TimeStart;    //ゲーム開始時のtime関数の値
int TimeCount;    //ゲーム開始からの経過秒数
int HitBombs;    //正解数のカウンタ
int FlagNum;    //旗の残り数
int GameCount;    //手数


// Linux環境依存・keybd()、getch()関数の代替処理Start

// ターミナルパラメータの設定
void begin_getch(void){
  struct termios t;
  tcgetattr(0, &t);
  TOrigin = t;
  t.c_lflag &= ~(ICANON | ECHO);
  t.c_cc[VMIN] = 0;
  t.c_cc[VTIME] = 0;
  tcsetattr(0, TCSANOW, &t);
}

// ターミナルパラメータの復元
void end_getch(void) {
  tcsetattr(0, TCSADRAIN, &TOrigin);
}

// getch代替関数本体(keybd関数の機能も内包・一定時間でループを抜ける。)
char getch(void){
 char c = 0;
 int sec = 1 / (POLL_DELAY / SECOND_RACIO);
 begin_getch();
   while(1) {
    usleep(POLL_DELAY);
    if((sec-- <= 0)||(read(0, &c, 1) != 0)) break;
   }
 end_getch();
 return c;
}
// Linux環境依存・keybd()、getch()関数の代替処理用End


// 指定範囲(min~max)の乱数値を返す。
int getRandom(int min,int max){
 return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}

// 2次元座標から一次元配列のインデックスを返す
int getIndex(int x,int y){
 return x + y * STAGE_X;
}

// 配列インデックス値から2次元座標の横位置を返す
int getX(int index){
 return index % STAGE_X;
}

// 配列インデックス値から2次元座標の縦位置を返す
int getY(int index){
 return index / STAGE_X;
}

// ステージ初期化
void initStage(void){
 int i;
 for(i=0;i<STAGE_ARRAY;i++) Stage[i] = CHR_CLOSE;
 for(i=0;i<BOMB_NUM;i++) Bombs[i] = 0;
}

// 爆弾配置
void setBomb(){
 int i,j;
 srand(time(NULL));
 for(i=0;i<BOMB_NUM;i++){
  int bi,ind = getIndex(X,Y),dup = 0;
  bi = getRandom(0,STAGE_ARRAY - 1);
  if(bi == ind){
   dup = 1;
  }else{
   for(j=0;j<i;j++){
    if(Bombs[j] == bi) dup = 1;
   }
  }
  if(dup == 0) Bombs[i] = bi;
  else i--;
 }
}

// ゲームパラメータ初期化
int initGame(void){
 X = STAGE_X / 2;
 Y = STAGE_Y / 2;
 TimeStart = time(NULL);
 TimeCount = 0;
 HitBombs = 0;
 FlagNum = BOMB_NUM;
 GameCount = 0;
 initStage();
 return GAME_BODY;
}

// ゲームフィールドの横幅中央に文字列を表示する。
void drawCenter(const char *c){
 int len = strlen(c);
 int i,pad = (SCREEN_X - len) / 2;
 char cPads[SCREEN_X],cBuf[MAX_STR_BUF];
 strcpy(cBuf,c);
 for(i=0;i<SCREEN_X;i++) cPads[i] = ' ';
 if((pad + len) > SCREEN_X){
  cBuf[SCREEN_X - pad] = '\0';
 }
 strcpy(cPads + pad,cBuf);
 puts(cPads);
}

// 枠の表示
void drawBorder(void){
 int i;
 for(i=0;i<SCREEN_X;i++){
  if((i == 0)||(i == STAGE_X + 1)) putchar(Chars[CHR_CORNER]);
  else putchar(Chars[CHR_H_BORDER]);
 }
 putchar('\n');
}

// ステージの表示
void drawStage(void){
 int x,y;
 for(y=0;y<STAGE_Y;y++){
  putchar(Chars[CHR_V_BORDER]);
  for(x=0;x<STAGE_X;x++){
   if((x == X)&&(y == Y)) printf(ESC_REVERSE);
   putchar(Chars[Stage[getIndex(x,y)]]);
   printf(ESC_NORMAL);
  }
  putchar(Chars[CHR_V_BORDER]);
  putchar('\n');
 }
}

// 画面全体の表示
void drawScreen(int face){
 printf(ESC_CLEAR);
 drawCenter(TITLE_STR);
 drawCenter(Faces[face]);
 drawBorder();
 drawStage();
 drawBorder();
 printf("Time : %04d Secs\n",TimeCount);
 printf("Count: %04d Times\n",GameCount);
 printf("Flag : %03d/%03d\n",FlagNum,BOMB_NUM);
 puts(HELP_STR);
}

// ヒントの取得
int getHint(int x,int y,int sw){
 int i,count = CHR_OPEN;
 for(i=0;i<BOMB_NUM;i++){
  const int bx = getX(Bombs[i]);
  const int by = getY(Bombs[i]);
  if(Bombs[i] == getIndex(x,y)) return CHR_BOMB;
  else if((bx >= (x - 1))&&(bx <= (x + 1))
     &&(by >= (y - 1))&&(by <= (y + 1))){
   if((sw)||(Stage[getIndex(bx,by)] != CHR_FLAG)) count++;
  }
 }
 return count;
}

// ヒントのないセルを自動的に開く
void autoOpen(const int x,const int y){
 int i;
 const int vx[] = { 0, 0,-1, 1,-1,-1, 1, 1};
 const int vy[] = {-1, 1, 0, 0,-1, 1,-1, 1};
 for(i=0;i<8;i++){
  int *stg;
  const int xx = x + vx[i];
  const int yy = y + vy[i];
  if((xx >= 0)&&(xx < STAGE_X)&&(yy >= 0)&&(yy < STAGE_Y)){
   stg = &Stage[getIndex(xx,yy)];
   if(*stg == CHR_CLOSE){
    *stg = getHint(xx,yy,FLAG_FALSE);
    if(*stg == CHR_OPEN) autoOpen(xx,yy);
   }
  }
 }
}

// ヒントを中心とした8マスにマークされていない爆弾がなければ周囲の8マスを開く
void nearOpen(){
 int *stg = &Stage[getIndex(X,Y)];
 if((*stg == CHR_OPEN)||(*stg > CHR_8)) return;
 if(getHint(X,Y,FLAG_TRUE) == 0) autoOpen(X,Y);
}

// 旗を置く
int putFlag(void){
 int *stg = &Stage[getIndex(X,Y)];
 if((*stg == CHR_CLOSE)&&(FlagNum > 0)){
  *stg = CHR_FLAG;
  FlagNum--;
  if(getHint(X,Y,FLAG_FALSE) == CHR_BOMB){
   HitBombs++;
   if(HitBombs >= BOMB_NUM) return GAME_CLEAR;
  }
 }else if(*stg == CHR_FLAG){
  *stg = CHR_CLOSE;
  FlagNum++;
  if(getHint(X,Y,FLAG_FALSE) == CHR_BOMB) HitBombs--;
 }
 return GAME_BODY;
}

// 目印を置く
int putMark(void){
 int *stg = &Stage[getIndex(X,Y)];
 if(*stg == CHR_CLOSE) *stg = CHR_MARK;
 else if(*stg == CHR_MARK) *stg = CHR_CLOSE;
 return GAME_BODY;
}

// 隠されているセルを開く
int openCell(void){
 int *stg = &Stage[getIndex(X,Y)];
 if(*stg == CHR_CLOSE){
  if(GameCount++ == 0) setBomb();
  *stg = getHint(X,Y,FLAG_FALSE);
  if(*stg == CHR_OPEN) autoOpen(X,Y);
  else if(*stg == CHR_BOMB) return GAME_OVER;
 }else{
  nearOpen();
 }
 return GAME_BODY;
}

// カーソルを上に移動
int moveUp(void){
 if(Y > 0) Y--;
 return GAME_BODY;
}

// カーソルを下に移動
int moveDown(void){
 if(Y < (STAGE_Y - 1)) Y++;
 return GAME_BODY;
}

// カーソルを左に移動
int moveLeft(void){
 if(X > 0) X--;
 return GAME_BODY;
}


// カーソルを右に移動
int moveRight(void){
 if(X < (STAGE_X - 1)) X++;
 return GAME_BODY;
}

// ゲームを終了する。
int exitGame(){
 puts(EXIT_STR);
 return GAME_EXIT;
}

// ゲーム更新処理
int updateGame(void){
 char c = getch();
 const char keyCode[] = {
  KEY_SPACE,
  KEY_ENTER,
  KEY_M,
  KEY_UP,
  KEY_DOWN,
  KEY_LEFT,
  KEY_RIGHT,
  KEY_E
 };
 int (*f[])(void) ={ 
  putFlag,
  openCell,
  putMark,
  moveUp,
  moveDown,
  moveLeft,
  moveRight,
  exitGame
 };
 int i;
 TimeCount = time(NULL) - TimeStart;
 if(TimeCount >= GOVER_TIME) return GAME_OVER;
 for(i=0;i<KEY_NUM;i++){
  if(toupper(c) == keyCode[i]) return f[i]();
 }
 return GAME_BODY;
}

// ゲームメインループ
int gameBody(void){
 drawScreen(GAME_BODY);
 return updateGame();
}

// ゲームクリア時の処理
int gameClear(void){
 drawScreen(GAME_CLEAR);
 puts(CLEAR_STR);
 getch();
 return GAME_EXIT;
}

// 全部の爆弾を表示する
void displayAllBombs(){
 int i;
 for(i=0;i<BOMB_NUM;i++) Stage[Bombs[i]] = CHR_BOMB;
}

// ゲームオーバー時の処理
int gameOver(void){
 displayAllBombs();
 drawScreen(GAME_OVER);
 puts(GOVER_STR);
 getch();
 return GAME_EXIT;
}

// メイン関数(エントリポイント)
int main(void){
 int gm = GAME_INIT;
 int (*f[])(void) = {initGame,gameClear,gameBody,gameOver};
 while(gm != GAME_EXIT) gm = f[gm]();
 return 0;
}
実行イメージ