C에서 AI를 사용한 TicTacToe
나는 최근에 다른 언어에 대한 경험이 있지만 C를 배우기 시작했습니다. 최근에 C의 Minimax 알고리즘을 사용하여 TicTacToe AI를 작성했습니다. C를 더 잘 작성할 수있는 방법을 알고 싶습니다.
#include <stdio.h>
#define X -1
#define O -2
#define MAX_SIZE 50 //This is the Max size of the Input Buffer
int turn = O;
int board[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int variations = 0; //To keep track of no. of variations the ai has seen
void copyBoard(int from[], int to[]){
for(int i = 0; i < 9; i++){
to[i] = from[i];
}
}
//gets line WITHOUT \n
int getl(char s[], int lim){
int c, i;
for(i = 0; i < lim-1 && (c = getchar()) != EOF && c != '\n'; i++)
s[i] = c;
s[i] = '\0';
return i;
}
//Converts char[] to int
int bufferToNum(char buffer[]){
int n = 0;
for(int i = 0; buffer[i] != '\0'; i++){
n = 10 * n + buffer[i] - '0';
}
return n;
}
//converts the board numbers to char to display
char boardToChar(int i){
int a = board[i];
if (a == X){
return 'X';
} else if (a == O){
return 'O';
} else {
return a + '0';
}
}
//prints board
void printBoard(){
printf("=============\n| %c | %c | %c |\n-------------\n| %c | %c | %c |\n-------------\n| %c | %c | %c |\n=============\n", boardToChar(0), boardToChar(1), boardToChar(2), boardToChar(3), boardToChar(4), boardToChar(5), boardToChar(6), boardToChar(7), boardToChar(8));
}
//alternates turn
void alternateTurn(){
if (turn == O){
turn = X;
} else if (turn == X){
turn = O;
}
}
//returns 1 if draw, return 0 if not a draw
int drawCheck(int l_board[]){
for(int i = 0; i < 9; i++){
if (l_board[i] == i+1){
return 0;
}
}
return 1;
}
//returns X if X won and O if O one and 0 if nobody winning
int winCheck(int l_board[]){
//Rows
for (int i = 0; i < 3; i++){
if (l_board[3*i] == l_board[3*i + 1] && l_board[3*i + 1] == l_board[3*i + 2]){
return l_board[3*i];
}
}
//Columns
for (int j = 0; j < 3; j++){
if (l_board[j] == l_board[3 + j] && l_board[3 + j] == l_board[6 + j]){
return l_board[j];
}
}
//Diagonal Top Left to Bottom Right
if (l_board[0] == l_board[4] && l_board[0] == l_board[8]){
return l_board[0];
}
//Diagonal Top Right to bottom Left
if (l_board[2] == l_board[4] && l_board[2] == l_board[6]){
return l_board[2];
}
return 0;
}
//1 if nothing is ther and 0 if something was already ther
int putInBoard(int l_board[], int pos, int newVal){
if (l_board[pos] == pos+1){
l_board[pos] = newVal;
return 1;
} else
{
return 0;
}
}
//X if X win, O if O win, 0 if draw, 1 if nothing
int gameState(int l_board[]){
int wc = winCheck(l_board);
if (wc == X){
return X;
} else if(wc == O){
return O;
} else {
if (drawCheck(l_board)){
return 0;
}
}
return 1;
}
void legalMoves(int l_board[], int output[]){
for(int i = 0; i < 9; i++){
if (l_board[i] == i+1){
output[i] = 1;
} else {
output[i] = 0;
}
}
}
int max(int a, int b){
return a>b ? a : b;
}
int min(int a, int b){
return a<b ? a : b;
}
//X is ai
int minimax(int l_board[], int depth, int maximising){
int gs = gameState(l_board);
variations++;
if (gs == X){
return 10;
} else if (gs == O){
return -10;
} else if (gs == 0){
return 0;
}
if (depth == 0){
return 0;
}
if (maximising){
//Its AI's Turn so it has to maximise
int val = -100;
int legalMovesArr[9];
legalMoves(l_board, legalMovesArr);
for (int i = 0; i < 9; i++){
if (legalMovesArr[i]){
int tempBoard[9];
copyBoard(l_board, tempBoard);
putInBoard(tempBoard, i, X);
val = max(minimax(tempBoard, depth-1, 0), val);
}
}
return val;
} else {
int val = 100;
int legalMovesArr[9];
legalMoves(l_board, legalMovesArr);
for (int i = 0; i < 9; i++){
if (legalMovesArr[i]){
int tempBoard[9];
copyBoard(l_board, tempBoard);
putInBoard(tempBoard, i, O);
val = min(minimax(tempBoard, depth-1, 1), val);
}
}
return val;
}
}
int ai(int l_board[], int depth){
int legalMovesArr[9];
legalMoves(board, legalMovesArr);
int val = -100;
int best_move = 0;
for (int i = 0; i < 9; i++){
if (legalMovesArr[i]){
int tempBoard[9];
copyBoard(l_board, tempBoard);
putInBoard(tempBoard, i, X);
int temp = minimax(tempBoard, depth-1, 0);
if (val <= temp){
val = temp;
best_move = i;
}
}
}
return best_move;
}
int main(){
printBoard();
int gameOn = 0;
char buffer[MAX_SIZE];
while(!gameOn){
if (turn == O){
printf("%c's turn: ", turn == X ? 'X' : 'O');
getl(buffer, MAX_SIZE);
int num = bufferToNum(buffer);
while (num <= 0 || num > 9){
printf("Please enter an integer between 1 and 9: ");
getl(buffer, MAX_SIZE);
num = bufferToNum(buffer);
}
if (putInBoard(board, num-1, turn)){
;
} else {
while(!putInBoard(board, num-1, turn)){
printf("Something already exists, Please enter a new number: ");
getl(buffer, MAX_SIZE);
num = bufferToNum(buffer);
}
}
} else {
putInBoard(board, ai(board, 8), X);
printf("Calculated %d variations\n", variations);
variations = 0;
}
printBoard();
alternateTurn();
int gs = gameState(board);
if (gs == X){
printf("X won!");
return 0;
} else if (gs == O){
printf("O won!");
return 0;
} else if (gs == 0){
printf("Draw!");
return 0;
}
}
return 0;
}
답변
일반 관찰
나는 이미 많은 좋은 프로그래밍 관행이 여기에서 따 랐음을 봅니다. 좋은 일을 계속하십시오.
대부분의 기능은 작으며 단일 책임 원칙을 따릅니다.
가능한 경우 라이브러리 기능 사용
이 함수 getl()
는 2 개의 표준 C 라이브러리 함수 fgets 및 strrchr을 사용하여 구현할 수 있습니다 .
이 fgets()
함수는 문자 입력을 사용하지 않고 한 번에 전체 문자 줄을 입력하며을 사용하면 문자 strrchr()
를 찾아서 \n
바꿀 수 있습니다.
드라이 코드
DRY 코드라고도하는 자기 반복 금지 원칙이라는 프로그래밍 원칙이 있습니다. 동일한 코드를 여러 번 반복하는 경우이를 함수로 캡슐화하는 것이 좋습니다. 반복을 줄일 수있는 코드를 반복 할 수있는 경우.
반복은 함수에서 발생합니다 int minimax(int l_board[], int depth, int maximising)
.
if (maximising) {
//Its AI's Turn so it has to maximise
int val = -100;
int legalMovesArr[9];
legalMoves(l_board, legalMovesArr);
for (int i = 0; i < 9; i++) {
if (legalMovesArr[i]) {
int tempBoard[9];
copyBoard(l_board, tempBoard);
putInBoard(tempBoard, i, X);
val = max(minimax(tempBoard, depth - 1, 0), val);
}
}
return val;
}
else {
int val = 100;
int legalMovesArr[9];
legalMoves(l_board, legalMovesArr);
for (int i = 0; i < 9; i++) {
if (legalMovesArr[i]) {
int tempBoard[9];
copyBoard(l_board, tempBoard);
putInBoard(tempBoard, i, O);
val = min(minimax(tempBoard, depth - 1, 1), val);
}
}
이 반복되는 코드는 자체 기능 일 수 있습니다.
반복되는 코드는 또한 함수를 너무 복잡하게 만듭니다.
복잡성
함수의 복잡성 minimax()
은 위에서 언급했습니다. 함수 main()
도 너무 복잡합니다 (너무 많이합니다). 프로그램의 크기가 커짐에 main()
따라 명령 줄을 구문 분석하는 함수 호출, 처리를 위해 설정된 함수 호출, 프로그램의 원하는 기능을 실행하는 함수 호출 및 주요 부분 이후 정리할 함수 호출로 사용을 제한해야합니다. 프로그램.
여기에 적용되는 단일 책임 원칙 (Single Responsibility Principle)이라는 프로그래밍 원칙도 있습니다. 단일 책임 원칙의 상태 :
모든 모듈, 클래스 또는 기능은 소프트웨어가 제공하는 기능의 단일 부분에 대한 책임이 있어야하며 해당 책임은 해당 모듈, 클래스 또는 기능에 의해 완전히 캡슐화되어야합니다.
전체 while (!gameOn)
루프는 자체 기능 내에 있어야합니다.
전역 변수 피하기
전역 변수를 사용하는 프로그램을 읽고, 쓰고, 디버그하고 유지하는 것은 매우 어렵습니다. 전역 변수는 프로그램 내의 모든 함수로 수정할 수 있으므로 코드를 변경하기 전에 각 함수를 검사해야합니다. C 및 C ++ 전역 변수는 네임 스페이스에 영향을 미치며 여러 파일에 정의 된 경우 링크 오류를 일으킬 수 있습니다. 이 stackoverflow 질문 의 답변은 더 자세한 설명을 제공합니다.
댓글에 대한 답변 업데이트
void game_loops()
{
if (turn == O) {
printf("%c's turn: ", turn == X ? 'X' : 'O');
char buffer[MAX_SIZE];
getl(buffer, MAX_SIZE);
int num = bufferToNum(buffer);
while (num <= 0 || num > 9) {
printf("Please enter an integer between 1 and 9: ");
getl(buffer, MAX_SIZE);
num = bufferToNum(buffer);
}
if (putInBoard(board, num - 1, turn)) {
;
}
else {
while (!putInBoard(board, num - 1, turn)) {
printf("Something already exists, Please enter a new number: ");
getl(buffer, MAX_SIZE);
num = bufferToNum(buffer);
}
}
}
else {
putInBoard(board, ai(board, 8), X);
printf("Calculated %d variations\n", variations);
variations = 0;
}
printBoard();
alternateTurn();
}
int main() {
printBoard();
int gs = 1;
while (gs == 1) {
game_loops();
gs = gameState(board);
}
switch (gs)
{
case X:
printf("X won!");
break;
case O:
printf("O won!");
break;
default:
printf("Draw!");
break;
}
return 0;
}
작은 리뷰
긴 코드 줄을 끊으십시오.
//prints board
void printBoard(){
printf("=============\n| %c | %c | %c |\n-------------\n| %c | %c | %c |\n-------------\n| %c | %c | %c |\n=============\n", boardToChar(0), boardToChar(1), boardToChar(2), boardToChar(3), boardToChar(4), boardToChar(5), boardToChar(6), boardToChar(7), boardToChar(8));
}
아마도
//prints board
void printBoard() {
printf("=============\n"
"| %c | %c | %c |\n"
"-------------\n"
"| %c | %c | %c |\n"
"-------------\n"
"| %c | %c | %c |\n"
"=============\n", //
boardToChar(0), boardToChar(1), boardToChar(2), //
boardToChar(3), boardToChar(4), boardToChar(5), //
boardToChar(6), boardToChar(7), boardToChar(8));
}
나를 //
위해 3 줄의 끝에는 해당 줄이 1 또는 2 줄로 자동 서식 지정되지 않습니다.