연습 노트
220323 함수를 사용한 빙고게임 - 내 코드와 선생님 코드 비교
hyrule
2022. 3. 25. 16:43
//내가 짠 코드.
#include <iostream>
#include <time.h>
enum class Difficulty
{
Min,
Easy,
Hard,
Max
};
void Shuffle(int* UserBoard, int* AiBoard);//빙고판 섞는 함수
void BoardPrint(int* UserBoard, int* AiBoard);
int UserInput(int* UserBoard, int* AiBoard, bool* Avbl);
void AiPick(int* UserBoard, int* AiBoard, bool* Avbl, int Stage);
void BingoLineCheck(int* UserBoard, int* AiBoard, bool* Avbl, int Stage, int& userBingo, int& aiBingo);
int main()
{
srand((unsigned int)time(0));
int random = rand();
//배열 선언(사용자, AI, 숫자모음)
int UserBoard[25] = {};
int AiBoard[25] = {};
bool Avbl[26] = {};//인덱스에 해당하는 숫자가 사용되었는지 확인용.1~25이므로 길이 26짜리 배열 생성
Shuffle(UserBoard, AiBoard);
//난이도 입력 받기
int Stage = (int)Difficulty::Min;
while (Stage <= (int)Difficulty::Min || Stage >= (int)Difficulty::Max)
{
std::cout << "* 난이도 설정(1 = Easy, 2 = Hard) : ";
std::cin >> Stage;
}
//빙고 갯수 체크를 위한 변수
int userBingo = 0, aiBingo = 0;
//게임 진행. 무한루프 구간
while (true)
{
system("cls");
std::cout << "Difficulty : ";
if (Stage == (int)Difficulty::Easy)
std::cout << "Easy" << std::endl;
else
std::cout << "Hard" << std::endl;
//1.빙고판 출력
BoardPrint(UserBoard, AiBoard);
//현재 스코어(빙고 수) 표시
std::cout << "\n유저 빙고 : " << userBingo << "\t\tAI 빙고 : " << aiBingo << std::endl;
//2.플레이어 숫자 입력 받고 빙고판에서 확인하기. 오류가 있거나 중복된 값을 입력했을 경우 다시 입력받기
int input = 0;
input = UserInput(UserBoard, AiBoard, Avbl);
if (0 == input)
{
system("cls");
std::cout << "게임 종료." << std::endl;
system("pause");
break;
}
//3.AI 턴.
//Easy 모드일 경우 남은 숫자 중에서 랜덤으로 추출
//Hard 모드일 경우 가장 빙고가 될 확률이 높은(줄마다 별 수, INT_MAX가 제일 많은 라인의 첫번째 수를 추출)
AiPick(UserBoard, AiBoard, Avbl, Stage);
//빙고갯수 확인
BingoLineCheck(UserBoard, AiBoard, Avbl, Stage, userBingo, aiBingo);
}
return 0;
}
void Shuffle(int* UserBoard, int* AiBoard)
{
//배열에 숫자 집어넣기
for (int i = 0; i < 25; ++i)
{
UserBoard[i] = i + 1;
AiBoard[i] = i + 1;
}
//배열 셔플
for (int i = 0; i < 100; ++i)
{
int Idx1 = rand() % 25;
int Idx2 = rand() % 25;
int temp = 0;
temp = UserBoard[Idx1];
UserBoard[Idx1] = UserBoard[Idx2];
UserBoard[Idx2] = temp;
Idx1 = rand() % 25;
Idx2 = rand() % 25;
temp = 0;
temp = AiBoard[Idx1];
AiBoard[Idx1] = AiBoard[Idx2];
AiBoard[Idx2] = temp;
}
}
void BoardPrint(int* UserBoard, int* AiBoard)
{
std::cout << "============= Player ========================================================== AI ===========" << std::endl;
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
if (UserBoard[5 * i + j] == INT_MAX)
std::cout << "*" << "\t";
else
std::cout << UserBoard[5 * i + j] << "\t";
}
std::cout << "\t\t\t";
for (int j = 0; j < 5; j++)
{
if (AiBoard[5 * i + j] == INT_MAX)
std::cout << "*" << "\t";
else
std::cout << AiBoard[5 * i + j] << "\t";
}
std::cout << std::endl;
}
}
int UserInput(int* UserBoard, int* AiBoard, bool* Avbl)
{
bool exit = false;//게임 종료 여부
bool isRepeat = false;//중복값 입력 여부
int input = 0;
while (true)
{
input = 0;
if (isRepeat)
{
std::cout << "\n* 중복값을 입력했습니다. 다시 입력하세요." << std::endl;
isRepeat = false;
}
std::cout << std::endl << "* 숫자를 입력하세요.(1~25, 0을 누르면 종료)\n>> ";
std::cin >> input;
if (input == 0)
exit = true;
else if (input > 0 && input <= 25)
{
//중복값 검사
if (Avbl[input])
{
isRepeat = true;
continue;
}
else
{
Avbl[input] = true;//해당 값이 사용되었음을 표시
for (int i = 0; i < 25; ++i)
{
if (UserBoard[i] == input)
UserBoard[i] = INT_MAX;
if (AiBoard[i] == input)
AiBoard[i] = INT_MAX;
}
break;
}
}
else//이외의 값이 입력되었을 경우 재실행
continue;
}
if (exit)
{
std::cout << "게임 종료." << std::endl;
input = 0;
return input;
}
return input;
}
void AiPick(int* UserBoard, int* AiBoard, bool* Avbl, int Stage)
{
switch ((Difficulty)Stage)
{
case Difficulty::Easy:
int random;
random = 0;
while (true)
{
//AI가 고를 수 있는 숫자 중에서 랜덤한 값을 선택한다.
random = rand() % 25;//25개중에 하나를 뽑아서
if (!Avbl[random])//해당 값이 아직 남아있으면
{
Avbl[random] = true;//사용했다고 표시한 후
break;//루프를 종료.
}
}
//유저와 AI의 빙고판에서 해당 값을 지운다.
for (int i = 0; i < 25; ++i)
{
if (UserBoard[i] == random)
UserBoard[i] = INT_MAX;
if (AiBoard[i] == random)
AiBoard[i] = INT_MAX;
}
std::cout << "AI's pick : " << random << std::endl;
break;
case Difficulty::Hard:
//AI의 빙고판에서
//가로줄, 세로줄, 왼쪽 위부터 오른쪽 아래 대각선, 오른쪽 위부터 왼쪽 아래 대각선 모두 확인하여 별의 수가 가장 적게 남아있는 줄을 선택.
int lineNum = 0; //가장 별이 많은 라인을 여기에 저장.
//가로줄 = 01234, 세로줄 = 56789, 좌상-우하 대각선 = 10, 우상 - 좌하 대각선 = 11
int starNum = 0; //줄별로 별의 갯수 기록.
int aiPick = 0;
for (int i = 0; i < 5; ++i)
{
//1.가로줄
int starNow = 0;//현재 줄의 별 갯수
for (int j = 0; j < 5; ++j)
{
if (INT_MAX == AiBoard[5 * i + j])
++starNow;
}
//매 줄마다 체크
if (5 == starNow)//이미 빙고인 줄은 체크하지 않는다
;
else if (4 == starNow)//만약 별 갯수가 4개라면 더이상 체크를 할 필요가 없다(제일 많으므로)
{
lineNum = i;
starNum = starNow;
break;//루프 중단.
}
else if (starNum < starNow)//만약 현재 줄의 별 갯수가 여태까지 별 갯수중 제일 많았다면
{
lineNum = i;
starNum = starNow;//현재 줄을 최고기록으로 등록하고 계속 반복
}
//2.세로줄
starNow = 0;//다시 초기화
for (int j = 0; j < 5; ++j)
{
if (INT_MAX == AiBoard[i + 5 * j])
++starNow;
}
//매 줄마다 체크
if (5 == starNow)//이미 빙고인 줄은 체크하지 않는다
;
else if (4 == starNow)//만약 별 갯수가 4개라면 더이상 체크를 할 필요가 없다(제일 많으므로)
{
lineNum = i + 5;
starNum = starNow;
break;//루프 중단.
}
else if (starNum < starNow)//만약 현재 줄의 별 갯수가 여태까지 별 갯수중 제일 많았다면
{
lineNum = i + 5;
starNum = starNow;//현재 줄을 최고기록으로 등록하고 계속 반복
}
}
//만약 starNum이 아직 4 미만이라면 대각선 체크도 시작.
if (starNum < 4)
{
int starNow = 0;
//3.좌상-우하 = 인덱스 00 11 22 33 44 = 0 6 12 18 24
for (int i = 0; i < 25; i += 6)
{
if (INT_MAX == AiBoard[i])
++starNow;
}
//매 줄마다 체크
if (5 == starNow)//이미 빙고인 줄은 체크하지 않는다
;
else if (starNum < starNow)//만약 현재 줄의 별 갯수가 여태까지 별 갯수중 제일 많았다면
{
lineNum = 10;
starNum = starNow;//현재 줄을 최고기록으로 등록하고 다음 단계로 이동
}
}
if (starNum < 4)
{
int starNow = 0;
//4.우상-좌하 = 인덱스 04 13 22 31 40 = 4 8 12 16 20
for (int i = 4; i < 21; i += 4)
{
if (INT_MAX == AiBoard[i])
++starNow;
}
//매 줄마다 체크
if (5 == starNow)//이미 빙고인 줄은 체크하지 않는다
;
else if (starNum < starNow)//만약 현재 줄의 별 갯수가 여태까지 별 갯수중 제일 많았다면
{
lineNum = 11;
starNum = starNow;//현재 줄을 최고기록으로 등록하고 다음 단계로 이동
}
}
//최종적으로 해당 라인에서 별이 되어있지 않은 가장 첫번째 값을 찾아 Ai가 선택한다.
if (lineNum <= 4)
{
for (int i = 0; i < 5; ++i)
{
if (INT_MAX != AiBoard[lineNum * 5 + i])
{
aiPick = AiBoard[lineNum * 5 + i];
break;
}
}
}
else if (lineNum >= 5 && lineNum <= 9)
{
for (int i = 0; i < 5; ++i)
{
if (INT_MAX != AiBoard[i * 5 + (lineNum - 5)])
{
aiPick = AiBoard[i * 5 + (lineNum - 5)];
break;
}
}
}
else if (10 == lineNum)
{
for (int i = 0; i < 25; i += 6)
{
if (INT_MAX != AiBoard[i])
{
aiPick = AiBoard[i];
break;
}
}
}
else
{
for (int i = 4; i < 21; i += 4)
{
if (INT_MAX != AiBoard[i])
{
aiPick = AiBoard[i];
break;
}
}
}
//유저와 AI의 빙고판에서 해당 값을 지운다.
for (int i = 0; i < 25; ++i)
{
if (UserBoard[i] == aiPick)
UserBoard[i] = INT_MAX;
if (AiBoard[i] == aiPick)
AiBoard[i] = INT_MAX;
}
//해당 값을 사용했다고 표시한다.
Avbl[aiPick] = true;
//디버그
//std::cout << lineNum << "\t" << starNum << "\t" << aiPick << std::endl;
std::cout << "AI's Pick : " << aiPick << std::endl;
break;
}
}
void BingoLineCheck(int* UserBoard, int* AiBoard, bool* Avbl, int Stage, int& userBingo, int& aiBingo)
{ //반환타입 없이 레퍼런스 역참조를 통해 반환을 해볼 예정.
//1.가로줄 + 세로줄
userBingo = 0, aiBingo = 0;
int userStarCount = 0, aiStarCount = 0;
for (int i = 0; i < 5; ++i)
{
userStarCount = 0, aiStarCount = 0;
//가로
for (int j = 0; j < 5; ++j)
{
if (INT_MAX == UserBoard[5 * i + j])
++userStarCount;
if (INT_MAX == AiBoard[5 * i + j])
++aiStarCount;
}
if (5 == userStarCount)
++userBingo;
if (5 == aiStarCount)
++aiBingo;
//세로
userStarCount =
0, aiStarCount = 0;
for (int j = 0; j < 5; ++j)
{
if (INT_MAX == UserBoard[i + 5 * j])
++userStarCount;
if (INT_MAX == AiBoard[i + 5 * j])
++aiStarCount;
}
if (5 == userStarCount)
++userBingo;
if (5 == aiStarCount)
++aiBingo;
}
//2.대각선(좌상-우하)
userStarCount = 0, aiStarCount = 0;
for (int i = 0; i < 25; i += 6)
{
if (INT_MAX == UserBoard[i])
++userStarCount;
if (INT_MAX == AiBoard[i])
++aiStarCount;
}
if (5 == userStarCount)
++userBingo;
if (5 == aiStarCount)
++aiBingo;
//3.대각선(우상-좌하)
userStarCount = 0, aiStarCount = 0;
for (int i = 4; i < 21; i += 4)
{
if (INT_MAX == UserBoard[i])
++userStarCount;
if (INT_MAX == AiBoard[i])
++aiStarCount;
}
if (5 == userStarCount)
++userBingo;
if (5 == aiStarCount)
++aiBingo;
system("pause");
}
//선생님이 짠 코드
#include <iostream>
#include <time.h>
enum class AIType
{
None,
Easy,
Hard,
Max
};
enum class GameProgress
{
PlayerWin,
AIWin,
Progress
};
// 인공지능을 선택하는 함수.
AIType SelectAIType()
{
int AIState = 0;
while (true)
{
system("cls");
std::cout << "1. 쉬움" << std::endl;
std::cout << "2. 어려움" << std::endl;
std::cout << "난이도를 선택하세요 : ";
std::cin >> AIState;
if (AIState > (int)AIType::None &&
AIState < (int)AIType::Max)
break;
}
return (AIType)AIState;
}
void SetNumber(int* Number, int* AINumber)
{
// 1 ~ 25까지의 숫자를 배열에 넣어준다.
for (int i = 0; i < 25; ++i)
{
Number[i] = i + 1;
AINumber[i] = i + 1;
}
// 숫자를 랜덤하게 배치해준다.
for (int i = 0; i < 100; ++i)
{
int Idx1 = rand() % 25;
int Idx2 = rand() % 25;
int Temp = Number[Idx1];
Number[Idx1] = Number[Idx2];
Number[Idx2] = Temp;
Idx1 = rand() % 25;
Idx2 = rand() % 25;
Temp = AINumber[Idx1];
AINumber[Idx1] = AINumber[Idx2];
AINumber[Idx2] = Temp;
}
}
void OutputNumber(int* Number, int* AINumber)
{
std::cout << "============ Player ================================================= AI =================" << std::endl;
for (int i = 0; i < 5; ++i)
{
for (int j = 0; j < 5; ++j)
{
if (Number[i * 5 + j] == INT_MAX)
std::cout << "*\t";
else
std::cout << Number[i * 5 + j] << "\t";
}
std::cout << "\t\t";
for (int j = 0; j < 5; ++j)
{
std::cout << "*\t";
/*if (AINumber[i * 5 + j] == INT_MAX)
std::cout << "*\t";
else
std::cout << AINumber[i * 5 + j] << "\t";*/
}
std::cout << std::endl;
}
}
GameProgress CheckGameState(int Line, int AILine)
{
std::cout << "Bingo Line : " << Line;
std::cout << "\t\t\t\t\t\t";
std::cout << "AI Bingo Line : " << AILine << std::endl;
if (Line >= 5)
{
std::cout << "Player Win" << std::endl;
return GameProgress::PlayerWin;
}
else if (AILine >= 5)
{
std::cout << "AI Win" << std::endl;
return GameProgress::AIWin;
}
return GameProgress::Progress;
}
bool ChangeNumber(int* Number, int CheckNumber)
{
for (int i = 0; i < 25; ++i)
{
if (Number[i] == CheckNumber)
{
// 숫자가 있다면 해당 숫자를 *로 바꿔준다.
Number[i] = INT_MAX;
return true;
}
}
return false;
}
int AISelectNumber(AIType AIState, int* AINumber)
{
// 플레이어가 선택한 숫자를 모두 바꾸었다면 인공지능이
// 선택할 수 있도록 한다.
switch ((AIType)AIState)
{
case AIType::Easy:
{
// 인공지능이 선택을 하기 위해서 현재 안바뀐 값을 저장할
// 배열을 만들어준다.
int SelectArray[25] = {};
// AI가 현재 선택할 수 있는 숫자가 몇개인지를 저장할 변수를
// 만들어준다.
int SelectCount = 0;
// 현재 입력 안된 값들을 찾아서 배열에 넣어준다.
SelectCount = 0;
for (int i = 0; i < 25; ++i)
{
// *이 아닌 일반 값일 경우
if (AINumber[i] != INT_MAX)
{
// 배열에 이 값을 넣어준다.
SelectArray[SelectCount] = AINumber[i];
++SelectCount;
}
}
return SelectArray[rand() % SelectCount];
}
case AIType::Hard:
{
// 전체 12줄 중 완성된 줄을 제외하고 나머지 미완성된 줄을
// 체크하여 *의 수가 가장 많은 줄을 찾는다.
int StarCount = 0;
int CheckLine = 0;
// Find는 한 줄에 별이 4개짜리 줄이 있을 경우
// true로 바꾸어서 대각선 줄을 체크 안하도록 해주는\
// 변수이다.
bool Find = false;
for (int i = 0; i < 5; ++i)
{
// 가로 한 줄의 별 수를 체크하기 위한 변수.
int LineStarCount = 0;
int LineStarCount1 = 0;
for (int j = 0; j < 5; ++j)
{
// 가로 줄 체크를 하는데 값이 *로 표시되는
// INT_MAX일 경우 이 줄의 별 수를 증가시킨다.
if (AINumber[i * 5 + j] == INT_MAX)
++LineStarCount;
// 세로 줄 체크를 하는데 값이 *로 표시되는
// INT_MAX일 경우 이 줄의 별 수를 증가시킨다.
if (AINumber[j * 5 + i] == INT_MAX)
++LineStarCount1;
}
// 현재의 가로 줄이 기존에 체크해두었던 줄의 별 수보다
// 크다면 현재 줄이 가능성이 가능 높은 줄이 된다.
// LineStarCount < 5 를 한 이유는 한 줄이 완성이
// 안되었을 경우에만 처리를 해야 하기 때문이다.
if (StarCount < LineStarCount && LineStarCount < 5)
{
StarCount = LineStarCount;
CheckLine = i;
// *이 4개라면 다른 줄을 체크하더라도 이 이상의
// 줄이 없으므로 이 줄을 선택하도록 한다.
if (StarCount == 4)
{
Find = true;
break;
}
}
// 현재의 세로 줄이 기존에 체크해두었던 줄의 별 수보다
// 크다면 현재 줄이 가능성이 가능 높은 줄이 된다.
// LineStarCount1 < 5 를 한 이유는 한 줄이 완성이
// 안되었을 경우에만 처리를 해야 하기 때문이다.
if (StarCount < LineStarCount1 && LineStarCount1 < 5)
{
StarCount = LineStarCount1;
CheckLine = i + 5;
// *이 4개라면 다른 줄을 체크하더라도 이 이상의
// 줄이 없으므로 이 줄을 선택하도록 한다.
if (StarCount == 4)
{
Find = true;
break;
}
}
}
// Find변수는 *이 4개짜리인 줄을 찾았을때 true가 되므로
// false일때만 대각선을 체크해주도록 한다.
if (!Find)
{
int LineStarCount = 0;
// 왼쪽 상단 -> 오른쪽 하단 대각선
for (int i = 0; i < 25; i += 6)
{
if (AINumber[i] == INT_MAX)
++LineStarCount;
}
if (StarCount < LineStarCount && LineStarCount < 5)
{
StarCount = LineStarCount;
CheckLine = 10;
// *이 4개라면 다른 줄을 체크하더라도 이 이상의
// 줄이 없으므로 이 줄을 선택하도록 한다.
if (StarCount == 4)
{
Find = true;
break;
}
}
if (!Find)
{
LineStarCount = 0;
// 오른쪽 상단 -> 왼쪽 하단 대각선
for (int i = 4; i < 21; i += 4)
{
if (AINumber[i] == INT_MAX)
++LineStarCount;
}
if (StarCount < LineStarCount && LineStarCount < 5)
{
StarCount = LineStarCount;
CheckLine = 11;
}
}
}
// 가로줄 중 하나가 최종 줄로 선정되었을 경우
if (CheckLine <= 4)
{
// 예를 들어 가로 2번째 줄이 선정되었다면 CheckLine은
// 1이 저장되어 있을 것이다.
// 이 경우 1 * 5 + 0 ~ 4 가 되므로
// 5 ~ 9 번인덱스를 의미하므로 가로 2번째 줄을
// 하나씩 체크를 하게 되는 것이다.
for (int i = 0; i < 5; ++i)
{
if (AINumber[CheckLine * 5 + i] != INT_MAX)
{
return AINumber[CheckLine * 5 + i];
}
}
}
// 세로줄 중 하나가 최종 줄로 선정되었을 경우
else if (CheckLine <= 9)
{
for (int i = 0; i < 5; ++i)
{
if (AINumber[i * 5 + (CheckLine - 5)] != INT_MAX)
{
return AINumber[i * 5 + (CheckLine - 5)];
}
}
}
// 왼쪽 상단 -> 오른쪽 하단
else if (CheckLine == 10)
{
for (int i = 0; i < 25; i += 6)
{
if (AINumber[i] != INT_MAX)
{
return AINumber[i];
}
}
}
// 오른쪽 상단 -> 왼쪽 하단
else
{
for (int i = 4; i < 21; i += 4)
{
if (AINumber[i] != INT_MAX)
{
return AINumber[i];
}
}
}
}
}
// 인공지능이 잘못되었을 경우 -1을 리턴하여 알려준다.
return -1;
}
int CheckLine(int* Number)
{
int Line = 0;
for (int i = 0; i < 5; ++i)
{
int CheckCount = 0, CheckCount1 = 0;
for (int j = 0; j < 5; ++j)
{
// 가로 줄 체크
if (Number[i * 5 + j] == INT_MAX)
++CheckCount;
// 세로 줄 체크
if (Number[j * 5 + i] == INT_MAX)
++CheckCount1;
}
// 가로 한 줄을 체크했는데 CheckCount 변수가
// 5라면 해당 줄은 모두 *로 변경되어 있다는 것이다.
if (CheckCount == 5)
++Line;
if (CheckCount1 == 5)
++Line;
}
int CheckCount2 = 0;
// 왼쪽 상단 -> 오른쪽 하단
for (int i = 0; i < 25; i += 6)
{
if (Number[i] == INT_MAX)
++CheckCount2;
}
if (CheckCount2 == 5)
++Line;
CheckCount2 = 0;
// 오른쪽 상단 -> 왼쪽 하단
for (int i = 4; i <= 20; i += 4)
{
if (Number[i] == INT_MAX)
++CheckCount2;
}
if (CheckCount2 == 5)
++Line;
return Line;
}
int main()
{
srand((unsigned int)time(0));
int Random = rand();
AIType AIState = SelectAIType();
// 1 ~ 25 까지의 숫자를 저장하기 위한 공간을 준비한다.
int Number[25] = {};
int AINumber[25] = {};
// 1 ~ 25사이의 숫자를 지정하고 섞어준다.
SetNumber(Number, AINumber);
int Line = 0, AILine = 0;
while (true)
{
system("cls");
OutputNumber(Number, AINumber);
GameProgress GameState = CheckGameState(Line, AILine);
if (GameState != GameProgress::Progress)
break;
std::cout << "Input Number(0 : Exit) : ";
int CheckNumber = 0;
std::cin >> CheckNumber;
if (CheckNumber == 0)
break;
else if (CheckNumber < 0 || CheckNumber > 25)
continue;
// 배열의 각 요소와 체크값을 비교하여 해당 값이 있는지
// 판단한다.
if (!ChangeNumber(Number, CheckNumber))
continue;
// 정상적인 숫자를 입력하여 플레이어의 숫자가 * 로 바뀌었을
// 경우 AI의 숫자에서도 해당 값을 찾아서 *로 바꾸어준다.
ChangeNumber(AINumber, CheckNumber);
CheckNumber = AISelectNumber(AIState, AINumber);
std::cout << "AI Select : " << CheckNumber << std::endl;
//system("pause");
// AI가 선택한 숫자를 *로 바꿔주도록 한다.
ChangeNumber(Number, CheckNumber);
ChangeNumber(AINumber, CheckNumber);
// 빙고 줄을 체크한다.
// 빙고 줄을 0으로 초기화를 하고 모든 줄을 체크해본다.
Line = CheckLine(Number);
AILine = CheckLine(AINumber);
}
return 0;
}
선생님은 구조체도 매개변수로 받고, 리턴값으로 주어서 자유롭게 사용하였음.