0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語 オセロを作ってみた(簡易AI付き)

Last updated at Posted at 2025-09-18

目次

はじめに

練習としてC言語でオセロを作りました。
一応簡易的な敵AIまで作ってあります。
知識不足、経験不足は目立つと思いますが
ロジックはある程度おもしろいと思うのでぜひ見て行ってください。

備わる機構

  • マスに置く機構
  • そこにおけるか置けないかを出す機構
  • パスするかを出す機構
  • 簡易的な敵AI機構
  • 毎ターン最後のクリアチェック機構

コード紹介

1,includeと関数の宣言

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

void map_making(int map[64],int can_put_map[64]);
int pass_check(int map[64],int *can_put_map,int player); 
void input(int map[64],int player);
int computer(int *map,int player,int i,int *numbers,int *turn_count_max);
int turn(int *map,int player,int cell,int *turn_count,int do_turn);
void turn_step(int *step,int cell);
int turn_min(int a,int b);
int turn2(int *map,int step[8],int direction[8],int player,int cell,int *turn_count,int do_turn);
int turn3(int near1,int near2,int near_,int player);
void turn_map(int *map,int direction,int cell,int player,int near1,int near2,int *turn_count,int do_turn);
int clear_check(int map[64]);
void last(int clear,int map[64],int can_put_map[64]);

※ヘッダアファイル:windowsは環境依存なのでwindouwsでない人が実行する場合は消してください

2,main関数

ここで基本的なゲームの進行を行います

  • 基本的な変数の宣言
  • ゲームが終了するまでの繰り返し
  • player変数の管理
  • パスが二度続いた時のreturn
  • pass_check, clear_check, map_making, input, last 関数の呼び出し
int main(void)
{
	int player = 0;
	int count = 0;
	int clear = 0;
	int pass,pass_count = 0;
	int can_put_map[64] = {0};
	int map[64] = {0};
	map[27] = 2;
	map[36] = 2;
	map[28] = 1;
	map[35] = 1;
	 srand((unsigned int)time(NULL));
	printf("\033[34m●\033[0mが\033[34mコンピュータ\033[0mです\n");
	do{
		player = count%2 + 1;
		pass = pass_check(map,can_put_map,player);											/*関数ジャンプ(pass)*/
		clear = clear_check(map);													/*関数ジャンプ(クリアしたか)*/
		if(pass == 1){
			count++;
			pass_count++;
			if(pass_count == 2){
				return 0;
			}
			printf("\033[0mパスされました\n");
			continue;
		}
		
		map_making(map,can_put_map);												/*関数ジャンプ(map)*/
		
		input(map,player);															/*関数ジャンプ(入力)*/
		
		count++;
		pass_count = 0;
		
	}while(clear == 0);
	
	last(clear,map,can_put_map);													/*関数ジャンプ(勝者表示)*/
	
	return 0;
}

3,map_making関数

ここではmapの描画を行います
用いる配列データは

  • map[64]      (マスごとの石を表す)
  • can_put_map[64]  (石を置けるマスを表す)

の二つです

void map_making(int map[64],int can_put_map[64]){
	int i;
	printf("   1  2  3  4  5  6  7  8 \n 1");
	for(i=0; i<64; i++){
		if(can_put_map[i] == 1){
			printf("\033[0m[\033[90m!\033[0m]");
		}else if(map[i] == 0){
			printf("\033[0m[ ]");
		}else if(map[i] == 1){
			printf("\033[0m[\033[31m○\033[0m]");
		}else if(map[i] == 2){
			printf("\033[0m[\033[34m●\033[0m]");
		}
		if(i%8 == 7){
			if(i/8 == 7){
				printf("\033[0m \n");
			}else{
				printf("\033[0m\n %d",i/8+2);
			}
		}
	}
	printf("\n");
}

4,pass_check関数

この関数は全マスをturn関数(詳しくは後述)にかけ、
置けるマスが一つもないときはパスすべきとして1を、
置けるマスがあるときは パスしなくてよいとして0を返します

int pass_check(int map[64],int *can_put_map,int player){
	int no_pass_point = 0;
	int i,a;
	for(i=0; i<64; i++){
		can_put_map[i] = 0;
		if(map[i] == 0){
			no_pass_point += turn(map,player,i,&a,0);
			if(turn(map,player,i,&a,0) != 0){
				can_put_map[i] = 1;
			}
		}
	}
	if(no_pass_point == 0){
		return 1;
	}else{
		return 0;
	}
}

5,input関数

プレイヤーの入力およびcomputer関数の呼び出しを行います
そのマスにおいてどこかひっくり返されるかのreturnはturn関数に頼っていますが
(それをdo{}while()の条件式に入れることで繰り返しを可能にします)
そのマスにすでに何か置かれている場合
入力を繰り返すというのはこの関数内で行っています

void input(int map[64],int player){
	int x;
	int y;
	int cell;
	int can_put = 0;
	int turn_count = 0;
	int computer_i = 0;
	int numbers[64];
	int turn_count_max[2];
	do{
		do{
			if(player == 1){
				printf("\033[31m○\033[0mさん、\n");
				
				do{
					printf("x座標は?(1~8):");
					scanf("%d",&x);
				}while(x<1 || 8<x);
				do{
					printf("y座標は?(1~8):");
					scanf("%d",&y);
				}while(y<1 || 8<y);
				cell = (y-1)*8 + (x-1);
			}else if(player == 2){
				cell = computer(map,player,computer_i,numbers,turn_count_max);
				computer_i++;
			}
		}while(map[cell] != 0);
		can_put = turn(map,player,cell,&turn_count,0);												/*関数ジャンプ*/
		turn_count = 0;
	}while(can_put == 0);
	turn(map,player,cell,&turn_count,1);
	
}

6,computer関数

少し冗長になってしまったcomputer関数です
この関数で簡易的な敵AIを作っています
四つ角、ランダムに残りの60マス全てを見て、
角は優先して取り、それ以外は最も多くのマスをひっくり返されるマスをとります
(同率のマスがあるとランダムで置かれます)
それぞれのひっくり返されるマスはturn関数にポインタ変数としてturn_countを渡すことで記録します
そのマスに何も置かれていないかという繰り返しはここに置かれています

int computer(int *map,int player,int i,int *numbers,int *turn_count_max){
	int i2;
	int turn_count;
	int i3, j, temp;
	int idx = 0;
	turn_count_max[0] = 0;
	turn_count_max[1] = 0;
	for(int n = 0; n < 64; n++) {
		if (n == 0 || n == 7 || n == 56 || n == 63){continue;}
			numbers[idx++] = n;
	}
	for(i3 = 59; i3 > 0; i3--) {
		j = rand() % (i3 + 1);
		temp = numbers[i3];
		numbers[i3] = numbers[j];
		numbers[j] = temp;
	}
	if(i==0){
		return 0;
	}else if(i==1){
		return 7;
	}else if(i==2){
		return 56;
	}else if(i==3){
		return 63;
	}else{
		for(i2=0; i2<60; i2++){
			turn_count = 0;
			
			turn(map,player,numbers[i2],&turn_count,0);
			if(turn_count_max[0] < turn_count){
				if(map[numbers[i2]] == 0){
					turn_count_max[0] = turn_count;
					turn_count_max[1] = numbers[i2];
				}
			}
		}
		
		Sleep(800);
		return turn_count_max[1];
	}
}

※Sleep関数はヘッダファイル:windowsによるものです
 #include <windows>を削除されたかたはこちらも削除をお願いします

7,turn関数

ついに来ましたturn関数
ここまでいろんな関数に組み込まれていましたが内容は書かれませんでした
ここからがロジックの重要部分を担います
しかしこれ自体の役割はあまりなく、

  • 基本的な変数や配列の宣言
  • turn_step関数を呼び出しdirection,step配列を整理
  • turn2関数の返値をreturnする

のみです

int turn(int *map,int player,int cell,int *turn_count,int do_turn){
	int direction[8] = {-8,1,8,-1,-7,9,7,-9};
	int step[8] = {0};
	turn_step(step,cell);
	
	return turn2(map,step,direction,player,cell,turn_count,do_turn);
}

8,turn_step関数

受け取ったstep関数を整理する関数です
置きたいマスにより縦横斜めそれぞれに何マス調べるべきかを出します
斜めのstepにはturn_min関数が使われます
これは後述しますが二つの数値を渡されより小さな数値を返す関数です

void turn_step(int *step,int cell){
	int i;
	step[0] = cell/8;
	step[1] = 8-(cell%8 + 1);
	step[2] = 8-(cell/8 + 1);
	step[3] = cell%8;
	step[4] = turn_min(step[0],step[1]);
	step[5] = turn_min(step[1],step[2]);
	step[6] = turn_min(step[2],step[3]);
	step[7] = turn_min(step[3],step[0]);
}

9,turn_min関数

二つの数値を渡されより小さな数値を返す関数です

int turn_min(int a,int b){
	if(a<b){
		return a;
	}
	if(a>=b){
		return b;
	}
}

10,turn2関数

turn_step関数により整理されたstep関数を使います
それぞれの方向でstep配列に記録された回数分置くマスから探索し、
もっとも置くマスから近い、

  • 白を near1 変数に
  • 黒を near2 変数に
  • 空白を near_ 変数に

代入します
その後はこれらの変数をturn3関数に渡し、turn3のreturnから
置けるときは1を、置けないときは0を返します
また、ついでにはturn_map関数に渡してmapを変化させています

int turn2(int *map,int step[8],int direction[8],int player,int cell,int *turn_count,int do_turn){
	int direction_i;
	int step_i;
	int near1;
	int near2;
	int near_;
	int turn_point = 0;
	for(direction_i=0; direction_i<8; direction_i++){
		near1 = 8;
		near2 = 8;
		near_ = 8;
		for(step_i=0; step_i < step[direction_i]; step_i++){
			if(map[cell + direction[direction_i]*(step_i+1)] == 1){
				if(step_i < near1){
					near1 = step_i+1;
				}
			}else if(map[cell + direction[direction_i]*(step_i+1)] == 2){
				if(step_i < near2){
					near2 = step_i+1;
				}
			}else{
				if(step_i < near_){
					near_ = step_i+1;
				}
			}
		}
		
		if(turn3(near1,near2,near_,player) != 0){
			turn_map(map,direction[direction_i],cell,player,near1,near2,turn_count,do_turn);
		}
		
		turn_point += turn3(near1,near2,near_,player);
	}
	if(turn_point == 0){
		return 0;
	}else{
		return 1;
	}
}

11,turn3関数

player,near1,near2,near3変数から相手コマをひっくり返せるかをreturnします
自分コマと相手コマ、空白コマ、
自分コマが相手コマより遠くにあるかつ、空白コマが自分コマより遠くにあるとき、
相手コマを挟めているとして1を返します(それ以外は0を返す)

int turn3(int near1,int near2,int near_,int player){
	if(player == 1){
		if(near1 > near2 && near1 < near_){
			return 1;
		}else{
			return 0;
		}
	}
	if(player == 2){
		if(near2 > near1 && near2 < near_){
			return 1;
		}else{
			return 0;
		}
	}
}

12,turn_map関数

turn3が1を返したときにのみ呼び出される関数です
自分コマのnear変数から実際にひっくり返す関数です
ちなみにturn_map関数ではcomputer関数でつかうturn_count変数を増加させています

void turn_map(int *map,int direction,int cell,int player,int near1,int near2,int *turn_count,int do_turn){
	int i;
	int max;
	if(player == 1){
		max = near1;
	}else if(player == 2){
		max = near2;
	}
	if(do_turn == 1){
		for(i=1; i<=max; i++){
			map[cell + i*direction] = player;
		}
		
		map[cell] = player;
	}
	for(i=1; i<=max; i++){
		(*turn_count)++;
	}
}

13,clear_check関数

それぞれのコマの数を数えることで勝敗を決めます

int clear_check(int map[64]){
	int i;
	int iend = 1;
	int score1 = 0;
	int score2 = 0;
	for(i=0; i<64; i++){
		if(map[i] == 1){
			score1++;
		}else if(map[i] == 2){
			score2++;
		}
		if(map[i] == 0){
			iend = 0;
		}
	}
	if(iend == 1){
		if(score1 > score2){
			return 1;
		}else if(score1 < score2){
			return 2;
		}else if(score1 == score2){
			return 3;
		}
	}else{
		return 0;
	}
}

14,last関数

clear_check関数により明らかになった勝敗をプリントする関数です

void last(int clear,int map[64],int can_put_map[64]){
	if(clear == 1){
		printf("\033[31m○\033[0mの勝利!\n");
	}else if(clear == 2){
		printf("\033[34m●\033[0mの勝利!\n");
	}else{
		printf("\033[0m引き分け\n");
	}
	map_making(map,can_put_map);
}

最後に

ここまで長い文章を読んでいただいてありがとうございました
これからはより強いAIを完成させるために精進していきます
改善点などあればぜひ教えていただけるととてもうれしいです

0
0
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?