본문 바로가기

Program Language and Algorithm

C++로 만드는 숫자 야구게임 (소프트웨어 끼리 싸우기 : Inter Process Communication)

반응형

C++로 만드는 숫자 야구 게임의 마지막 글 입니다. 이전 글에서 문제를 내는 알고리즘과 문제를 푸는 알고리즘을 만들었습니다.

2021.03.29 - [Program Language and Algorithm] - C++로 만드는 숫자 야구 게임 : 문제를 내는 알고리즘

 

C++로 만드는 숫자 야구 게임 : 문제를 내는 알고리즘

 숫자 야구 게임은 소프트웨어 엔지니어링에 대해 배우다 보면 한 번씩은 보게 되고, 또 만들게 되는 간단한 숫자 게임입니다. 문제를 내는 알고리듬은 반복문이나 조건문, 입출력 등의 기본 기

sbinroom.tistory.com

2021.03.30 - [Program Language and Algorithm] - C++로 만드는 숫자 야구 게임 : 문제를 푸는 알고리듬

 

C++로 만드는 숫자 야구 게임 : 문제를 푸는 알고리듬

 이전 글에서는 초록이(문제를 내는 알고리듬)를 만들었으니 이번엔 파랑이를 만들어 주려고 합니다. 2021.03.29 - [Program Language and Algorithm] - C++로 만드는 숫자 야구 게임 : 문제를 내는 알고리즘 C+

sbinroom.tistory.com

 이번엔 이 두 프로그램이 게임을 하게 해보겠습니다. 재미 있는 주제죠? 컴퓨터 대 컴퓨터, 알고리듬 대 알고리듬....만약 체스나, 장기, 바둑 이라면 세기의 대결 이겠지만, 숫자 야구 게임은 푸는 쪽이 유리하니 전혀 흥미진진하지 않습니다.

 이 소프트웨어를 기획하고, 만든 이유는 그냥 Inter Process Comunication(IPC)을 하고 싶었어요. 리눅스 시스템에서 IPC를 하는 방식은 여러가지가 있습니다. PIPE, semaphore, share memory, massage queue 등 입니다. 이 글에서는 message queue를 이용합니다. 메시지 큐에 대한 설명은 너무 길어서 다른 글로 별도로 포스팅하였습니다. 이전 글이나 다른 분의 글을 참조해 주시기 바랍니다.

2021.04.08 - [Linux & Mac] - Inter Process Communication : Message Queue 사용법

 

Inter Process Communication : Message Queue 사용법

 다양한 소프트웨어를 만들다 보면, 소프트웨어 간 통신이 필요한 경우가 생기기도 합니다. 이때 이용하는 소프트웨어 공학 기술이 Inter Process Communication(IPC)입니다. 이 글에서는 IPC 기법 중 하

sbinroom.tistory.com

소프트웨어 통신 설계

 메시지 큐의 사용 법은 위 링크의 글에서 설명 하였으니, 통신 방식을 간략히 설명 한후, 예제로 바로 넘어 가겠습니다. 통신에는 두개의 key를 이용했습니다. 두 소프트웨어에서 각각 송신과 수신을 담당 합니다. 같은 key를 이용해도 되지만, 개념을 명확히 해주기 위해 key를 두개로 만들고, 당연히 id도 송수신 별로 2개 이며, 하나의 id는 수신만 하고, 하나는 송신만 하는 구조 입니다.

 구동 시 파랑이가 몇개의 숫자를 사용할 것인지 입력 받고, 이 값을 초록이에게 보내줍니다. 이후 각각 초기화 이후 파랑이가 추측되는 수열을 보내면, 초록이가 결과를 계산해서 전송해 주는 구조 입니다.

 이전 글들을 보시면 초록이는 3아웃제와 9회의 제한 조건이 있으나, 파랑이에는 만들지 않았습니다. 이 차이점을 맞춰 주어야 겠죠. 먼저 3아웃제는 편의상 삭제 합니다. (3 아웃을 당할 가능성이 없으니까요. ) 9회의 기회는 정상적인 상황(숫자의 갯수 3개 이상, 7개 이하)에서는 반드시 9회 안에 풀게 되어 있어서 문제가 되지 않습니다. 지만 그 외의 경우에는 문제가 됩니다. 이 글에서는 해당 문제를 해결 하지 않겠습니다. 필요하시면 수정해 보시는 것도 좋겠네요.

문제를 내는 소프트웨어 (초록이)

 이전 글에서 만든 초록이가 아래와 같은 코드로 변경 되었습니다.

#include <iostream>
#include <vector>
#include <string>
#include <cstdlib>
#include <ctime>

#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>

using namespace std;

typedef struct dlStruct{
    long mType;
    int data;
}dlData;

typedef struct inStruct{
    long mType;
    int data[9];
}inData;

typedef struct judStruct{
    long mType;
    int data[2];
}judData;

// 정답 만들기.
void makeAnswer(vector<int> &answer, int dL){
    //정답의 크기(dL) 지정. 
    answer.resize(dL);

    // 정답이 될수 있는 후보들(0-9)을 저장하기 위한 벡터
    vector<int> cand(10, 0);
    for(int i = 1 ; 10 > i ; i++){
	cand[i] = i;
    }

    // 정답을 만들기 위한 난수
    srand(time(nullptr));

    // 정답을 만듦.
    for(int i = 0 ; dL > i ; i++){
	int idx = rand() % cand.size();

	answer[i] = cand[idx];

	cand.erase(cand.begin() + idx);
    }

    // 후보군 삭제.
    cand.clear();

}

// 사용자 입력 받기.
// in : 입력 받은 데이터 반환 (call by reference)
// dL : 게임의 난이도.
int getInput(inData &in, int dL, int msgid){

    msgrcv(msgid, &in, sizeof(in.data), 0, 0);

    return 0;
}

// 정답 검사
// jud : 결과
// in : 입력
// ans : 정답
// dL : 난이도
bool judge(judData &jud, inData &in, vector<int> &ans, int dL){
    bool res = false;
    // strike
    jud.data[0] = 0;
    // ball
    jud.data[1] = 0;

    // 단순 비교를 통한 결과 계산.
    for(int i = 0 ; dL > i ; i++){
	for(int j = 0 ; dL > j ; j++){
	    if(ans[i] == in.data[j]){
		res = true;
		if(i == j){
		    jud.data[0]++;
		}else{
		    jud.data[1]++;
		}
	    }
	}
    }

    // res 가 false 면 아웃.
    return res;
}

int main(int argc, char ** argv){

    key_t keySend = 12300;
    key_t keyRcv  = 12301;

    int msgIdSend;
    int msgIdRcv;

    // 난이도
    int dL;

    // 정답
    vector<int> ans;
    // 사용사 입력
    inData in;

    dlData dlVar;
    // 결과 : 0 = 스트라이크 1 = 볼
    judData jud;

    jud.mType = 1;

    // message queue open
    msgIdSend	= msgget(keySend, IPC_CREAT|0666);
    msgIdRcv	= msgget(keyRcv, IPC_CREAT|0666);

    msgrcv(msgIdRcv, &dlVar, sizeof(dlVar.data), 0, 0);

    dL	= dlVar.data;
    // 정답 만들기
    makeAnswer(ans, dL);

    cout << "answer : " ;
    for(int i = 0 ; dL > i ; i++){
	cout << ans[i] << " ";
    }
    cout << endl;

    // 9번의 기회 제공
    for(int t = 1 ; 10 > t ; t++){
	cout << "Round " << t << "/9 : ";
	if(1 == getInput(in, dL, msgIdRcv)){
	    break;
	}

	for(int i = 0 ; dL > i ; i++){
	    cout << in.data[i] << " ";
	}
	cout << endl;

	if(judge(jud, in, ans, dL)){
	    cout << jud.data[0] << " Strike " << jud.data[1] << " Ball" << endl;
	}else{
	    cout << "Out" << endl;
	}

	if(0 != msgsnd(msgIdSend, &jud, sizeof(jud.data), IPC_NOWAIT)){
	    cout << "Error" << endl;
	}
	if(dL == jud.data[0]){
	    cout << t << " Round" << endl;
	    cout << "You Win !! Congratulation" << endl;
	    msgctl(msgIdRcv, IPC_RMID, NULL);
	    return 0;
	}
    }
    cout << "You Lost ..." << endl;

    msgctl(msgIdRcv, IPC_RMID, NULL);

    return 0;
}

 먼저 통신을 위한 key 값은 12300과 12301입니다. 이 초록는 12300을 보내는 키로 사용합니다. 데이터 송수신을 위한 구조체는 3가지가 이용됩니다. 첫번째는 숫자의 갯수를 송수신하기 위한 구조체(dlData)이고, 두번째는 추론한 수열을 송수신 하기 위한 구조체(inData)이고, 마지막은 결과를 송수신하기 위한 구조체(judData) 입니다. 각각 1개 9개 2개 크기의 int 배열을 사용합니다.

 초기화 과정에는 초록이가 송신하는 결과 전송을 위한 구조체 변수(jud)의 type을 1로 설정(0이면 통신이 안됨)한 후, 송수신을 위한 id를 받고, 숫자의 갯수를 수신합니다. 이후 정답을 만들어 줍니다.

 구동 과정에서는 파랑이가 보내준 수열을 분석해서 결과를 전송하는 과정을 반복합니다.

문제를 푸는 소프트웨어 (파랑이)

 이글에서 이용하는 파랑이는 수열의 정답 후보를 생성할 때 queue를 이용합니다. 다른 방법을 알고 싶으시면 이전 글을 봐주세요.

2021.04.02 - [Program Language and Algorithm] - 중복 없는 수열 만드는 알고리즘 ( C++로 만드는 숫자 야구 게임 )

 

중복 없는 수열 만드는 알고리즘 ( C++로 만드는 숫자 야구 게임 )

이번 글은 지난번 숫자 야구 게임 만들기에서 이야기했던 중복 없는 수열 만드는 알고리즘입니다. 2021.03.29 - [Program Language and Algorithm] - C++로 만드는 숫자 야구 게임 : 문제를 내는 알고리즘 C++로.

sbinroom.tistory.com

#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <cstdlib>
#include <ctime>

#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>

typedef struct dlStruct{
    long mType;
    int data;
}dlData;

typedef struct inStruct{
    long mType;
    int data[9];
}inData;

typedef struct judStruct{
    long mType;
    int data[2];
}judData;

using namespace std;

void makeCandQueue(vector<vector<int>> &cand, queue<int> &iCand, vector<int> &cCand, int idx, int dL){
    if(idx == dL){
	cand.push_back(cCand);
	return;
    }

    for(int i = 0 ; iCand.size() > i ; i++){
	cCand[idx] = iCand.front();
	iCand.pop();

	makeCandQueue(cand, iCand, cCand, idx+1, dL);

	iCand.push(cCand[idx]);
    }
}

// 후보군을 만드는 함수
// cand : 후보군 저장
// dL : 숫자수.
void makeCand(vector<vector<int>> &cand, int dL){
    queue<int> iCand;
    vector<int> cCand(dL, 0);

    for(int i = 0 ; 10 > i ; i++){
	iCand.push(i);
    }

    makeCandQueue(cand, iCand, cCand, 0, dL);
}

// 정답 검사
// jud : 결과
// in : 입력
// ans : 정답
// dL : 난이도
void judge(vector<int> &jud, vector<int> &in, vector<int> &ans, int dL){
    // strike
    jud[0] = 0;
    // ball
    jud[1] = 0;

    // 단순 비교를 통한 결과 계산.
    for(int i = 0 ; dL > i ; i++){
	for(int j = 0 ; dL > j ; j++){
	    if(ans[i] == in[j]){
		if(i == j){
		    jud[0]++;
		}else{
		    jud[1]++;
		}
	    }
	}
    }
}

// 후보 추리기 : 추측한 답과 후보들과의 정답 판정을 통해. 초록이가 해준 판정과 같으면 유지. 아니면 삭제
// cand : 정답 후보
// jud : 초록이의 판정
// iIdx : 추측했던 후보 idx
// dL : 난이도
void removeCand(vector<vector<int>> &cand, judData jud, int iIdx, int dL){
    // 각 후보자의 삭제 여부 : false 삭제, true 유지
    vector<bool> cs(cand.size(), false);

    // 후보와 추측의 비교 결과 저장
    vector<int> iJud(2);

    // 모든 후보군에 대해서 추측 값과의 정답을 얻어 초록이의 판정과 비교 : 같은 경우 cs[i] = true
    for(int i = 0 ; cand.size() > i ; i++){
	judge(iJud, cand[i], cand[iIdx], dL);

	if(iJud[0] == jud.data[0] && iJud[1] == jud.data[1]){
	    cs[i] = true;
	}
    }

    // cs가 true인 경우를 남기고 전체 삭제. 방금 이용한 후보도 삭제.
    cs[iIdx] = false;

    int idx = 0;
    for(int i = 0 ; cand.size() > i ; i++){
	if(cs[i]){
	    for(int j = 0 ; dL > j ; j++){
		cand[idx][j] = cand[i][j];
	    }
	    idx++;
	}
    }

    cand.erase(cand.begin()+ idx, cand.end());

}

int main(int argc, char ** argv){

    key_t keySend = 12301;
    key_t keyRcv  = 12300;

    int msgIdSend;
    int msgIdRcv;


    // 난수 발생 초기화
    srand(time(nullptr));


    // 난이도 설정.
    int dL;

    if(2 >argc){
	dL = 3;
    }else{
	dL = atoi(argv[1]);

	if(3 > dL){
	    dL = 3;
	}
    }

    inData in;
    // 후보군 저장
    vector<vector<int>> cand;
    // 초록이의 판정
    judData jud;

    dlData dlVar;

    in.mType = 2;
    dlVar.mType = 3;

    // message queue open
    msgIdSend	= msgget(keySend, IPC_CREAT|0666);
    msgIdRcv	= msgget(keyRcv, IPC_CREAT|0666);

    dlVar.data = dL;
    msgsnd(msgIdSend, &dlVar, sizeof(dlVar.data), 0);

    // 후보군 생성
    makeCand(cand, dL);

    while(0 < cand.size()){
	// 추측하는 정답은 난수로 결정.
	int idx = rand() % cand.size();

	// 추측 값 출력.
	cout << "input : ";
	for(int i = 0 ; dL > i ; i++){
	    cout << cand[idx][i] << " ";
	}
	cout << endl;

	// 입력 전송
	for(int i = 0 ; dL > i ; i++){
	    in.data[i] = cand[idx][i];
	}
	msgsnd(msgIdSend, &in, sizeof(in.data), 0);

	// 결과 수집. 스트라이크 and 볼
	msgrcv(msgIdRcv, &jud, sizeof(jud.data), 0, 0);


	cout << "judge : ";
	for(int i = 0 ; 2 > i ; i++){
	    cout << jud.data[i] << " ";
	}
	cout << endl;

	// 종료 조건. 정답..
	if(dL == jud.data[0]){
	    break;
	}

	// 후보군 정리.
	removeCand(cand, jud, idx, dL);
    }

    msgctl(msgIdRcv, IPC_RMID, NULL);

    return 0;
}

 파랑이는 숫자의 갯수를 입력 변수로 받아 줍니다. 이렇게 바꾼 이유는 여러번 구동 시켜서 결과를 취합해 보고 싶었기 때문 입니다. 필요 하시면 이전 글과 같이 구동 후 사용자의 입력을 받게 바꾸시면 됩니다.

 초록이와 동일하게 메시지 큐 이용을 위한 구조체들을 만들어 주고, 송신을 할 구조체 변수들의 type을 각각 2와 3으로 지정해줍니다. (다시 언급 하지만 0이면 통신이 안됩니다.) 그후, 숫자의 갯수를 전송해 준뒤, 초기화를 해줍니다.

 구동 중에는 예상되는 수열을 송신하고, 결과를 수신해서, 답안을 찾습니다.

구동 결과

 아래 스냅숏은 숫자의 갯수를 3,5,7로 각각 지정했을 때 구동 결과 입니다.

 두 소프트웨어가 열심히 통신하면서 놀고 있죠. 아래 스크립트는 두 프로그램을 수회 구동 할때 이용 하는 스크립트 입니다.

for i in $(seq 1 1 10)
do
./baseBall
done

 

for i in $(seq 1 1 10)
do
./baseBallSolver 5
done 

위 스크립트를 이용하면 아래 스냅숏과 같이 숫자의 갯수가 5인 게임을 10회 수행 합니다. (아래 스냅숏은 3회로 줄였습니다.)

 100회씩 구동해서 보면 숫자의 갯수가 3~7 이라면, 문제는 반드시 풀립니다.

 그럼 이만 마치겠습니다.

 감사합니다.

반응형