개인 기록/삼성 육목 알고리즘

프로젝트 리뷰 1편 - 2017년 삼성전자 DS 부문 육목 알고리즘 대회

LiDARian 2021. 2. 20. 20:46
반응형

2017년에 삼성 DS부문에서 진행했던 대회

 

 

대학 1학년 때, 아는 것도 없이 과동기 3명이서 순수 패기로만 참여했었던 대회이다. 그래서 예선 광탈을 했던 기억, 가독성 나쁘게 코드를 써서 동기에게 한소리 들은 기억, 잘하는 동기를 보고 감탄했던 기억, Bitbucket 잘 못써서 힘들었던 기억 등... 본선은 못갔지만 배운 건 꽤 있었던 것 같다.

 

당시 제작한 Weigting Module. 내가 쓰면, 동기가 고치고. 이 과정을 되게 많이 반복했었다. 당시 그만큼 내 코드엔 문제가 많았다.

//외부배열 board[][] 존재
int Boardweighting[BOARD_SIZE][BOARD_SIZE]={0};
int Ourteamrow=0,Ourteamcol=0;
int Oppositerow=0,Oppositecol=0;


int row=0,col=0;
 //--------------- 돌 주변 좌표에 가중치 1 세팅 -----------------------------
	void BWClear(){		//가중치 초기화 함수
		int i=0,j=0;
		for(i=0;i<BOARD_SIZE;i++){
			for(j=0;j<BOARD_SIZE;j++){
				Boardweighting[i][j]=0;
			}
		}
	)
 
 int isOur(int x, int y)
{
	return x >= 0 && y >= 0 && x < width && y < height && board[x][y] == 1;
}

int isOp(int x, int y)
{
	return x >= 0 && y >= 0 && x < width && y < height && board[x][y] == 2;
}
 
 
 
 
	for(row=0;row<BOARD_SIZE;row++){
      for(col=0;col<BOARD_SIZE;col++){
               // LEFT →
         if( col < BOARD_SIZE-1 && board[row][col+1]==1 && Boardweighting[row][col]<1 )
            Boardweighting[row][col]=1;
         else   // RIGHT ← 
         if( col > 0 && board[row][col-1]==1  && Boardweighting[row][col]<1)
            Boardweighting[row][col]=1;
         else   // DOWN ↓
         if( row < BOARD_SIZE-1 && board[row+1][col]==1 && Boardweighting[row][col]<1)
            Boardweighting[row][col]=1;
         else   // UP ↑
         if( row > 0 && board[row-1][col]==1 && Boardweighting[row][col]<1)
            Boardweighting[row][col]=1;
         else // ↘
         if( (row < BOARD_SIZE-1 && col < BOARD_SIZE-1) && board[row+1][col+1] == 1  && Boardweighting[row][col]<1)
            Boardweighting[row][col]=1;      
         else // ↖
         if( (row > 0 && col > 0) && board[row-1][col-1] == 1  && Boardweighting[row][col]<1)
            Boardweighting[row][col]=1;
         else // ↗
         if( (row > 0 && col < BOARD_SIZE-1) && board[row-1][col+1] == 1  && Boardweighting[row][col]<1)
            Boardweighting[row][col]=1;
         else // ↙
         if( (row < BOARD_SIZE-1 && col > 0) && board[row+1][col-1] == 1  && Boardweighting[row][col]<1)
            Boardweighting[row][col]=1;
		
		
		
   //-------------- 돌의 개수에 의한 일반 가중치 -------------------
   for(row=0;row<BOARD_SIZE;row++){
      for(col=0;col<BOARD_SIZE;col++){
 
            // RIGHT →
         if( col < 15 
               && Boardweighting[row][col] ==1     //현재 가중치가 설정된 위치 
			   && isOur(row,col+1) == 1 ){   //right에 돌이 있는 경우
             
            count=0;
            tempPlayer=board[row][col+1] ;
 
				for(i=col+1; i<15;i++){
					if( board[row][i] == tempPlayer )
						count++;
				else
					break;
				}
 
            if(Boardweighting[row][col] < count){   // 해당 위치 가중치가 count 보다 작다면
                  Boardweighting[row][col]=count;
            }
 
         }
        
            // LEFT ← 
         if( col > 0 
               && Boardweighting[row][col] == 1       //현재 가중치가 설정된 위치
               && isOur(row,col-1) == 1){   //left에 돌이 있는 경우
             
				count=0;
				tempPlayer=board[row][col-1] ;
 
				for(i=col-1; i>=0;i--){
					if( board[row][i] == tempPlayer )
					count++;
				else
					break;
				}
 
          
            if(Boardweighting[row][col] < count){   // 해당 위치 가중치가 count 보다 작다면
                  Boardweighting[row][col]=count;
            }
 
 
         }
		 
            // DOWN ↓
         if( row < 15 
            && Boardweighting[row][col] ==1          //현재 가중치가 설정된 위치
            && isOur(row+1,col) == 1){      //down에 돌이 있는 경우
             
            count=0;
            tempPlayer=board[row+1][col] ;
 
				for(i=row+1; i<15 ;i++){
					if( board[i][col] == tempPlayer )
					count++;
					else
						break;
				}
 
			if(Boardweighting[row][col] < count){   // 해당 위치 가중치가 count 보다 작다면
                  Boardweighting[row][col]=count;
            }
 
 
 
         }
            // UP ↑
         if( row > 0 
               && board[row][col] == 1       //현재 가중치가 설정된 위치
               && isOur(row-1,col) == 1){   //up에 돌이 있는 경우
 
				count=0;
				tempPlayer=board[row-1][col] ;
 
					for(i=row-1; i>=0 ;i--){
						if( board[i][col] == tempPlayer )
						count++;
						else
							break;
					}
 
            if(Boardweighting[row][col] < count){   // 해당 위치 가중치가 count 보다 작다면
                  Boardweighting[row][col]=count;
            }
 
 
         }
          // ↘ right down
         if( (row < 15 && col < 15)  
            && board[row][col] == 1       // 현재 가중치가 설정된 위치
            && isOur(row+1,col+1) == 1)   // right-down에 돌이 있는 경우
         {
            count=0;
            tempPlayer=board[row+1][col+1] ;
 
				for(i=1;i<5;i++){
					if((row+i<15 && col+i<15) && (board[row+i][col+i]== tempPlayer))
					count++;
					else
						break;
            }
 
            if(Boardweighting[row][col] < count){   // 해당 위치 가중치가 count 보다 작다면
                  Boardweighting[row][col]=count;
            }
 
 
         }
          // ↖ left up
         if( (row > 0 && col > 0)  
            && board[row][col] == 1       // 현재 가중치가 설정된 위치
            && isOur(row-1,col-1) == 1){ // left-up에 돌이 있는 경우
 
            count=0;
            tempPlayer=Board[row-1][col-1] ;
 
				for(i=1;i<5;i++){
					if((row-i>=0 && col-i>=0) && (board[row-i][col-i]== tempPlayer))
					count++;
					else
						break;
            }
 
            if(Boardweighting[row][col] < count){   // 해당 위치 가중치가 count 보다 작다면
                  Boardweighting[row][col]=count;
            }
 
 
 
         }
          // ↗ right up
         if( (row > 0 && col < 15)  
            && board[row][col] == 1          // 현재 가중치가 설정된 위치
            && isOur(row-1,col+1) == 1)      // right-up에 돌이 있는 경우
         {
            count=0;
            tempPlayer=board[row-1][col+1] ;
 
            for(i=1;i<10;i++){
               if((row-i>=0 && col+i<15) && (board[row-i][col+i]== tempPlayer))
                  count++;
               else
                  break;
            }
 
            if(Boardweighting[row][col] < count){   // 해당 위치 가중치가 count 보다 작다면
                  Boardweighting[row][col]=count;
            }
 
         }
          // ↙ left down
         if( (row < 15 && col > 0)  
               && board[row][col] == 1         // 현재 가중치가 설정된 위치
               && isOur(row+1,col-1) == 1)  // left-down에 돌이 있을 경우
         {
            count=0;
            tempPlayer=board[row+1][col-1] ;
 
            for(i=1;i<5;i++){
               if((row+i<15 && col-i>0) && (board[row+i][col-i]== tempPlayer))
                  count++;
               else
                  break;
            }
 
            if(Boardweighting[row][col] < count){   // 해당 위치 가중치가 count 보다 작다면
                  Boardweighting[row][col]=count;
            }
 
         }
          
      }
   }
   
   
   void GetAttackPos(int board[][BOARD_SIZE], int* row, int* col){		//parameter에 각각 Boardweighting, Ourteamrow, Ourteamcol
 
   int Max=0;
 
   for(int i=0;i< 15; i++){
      for(int j=0;j< 15; j++){
          
         if(board[i][j] > Max && board[i][j] < PLAYER){
            Max = board[i][j];
            *row =i;
            *col =j;		//Ourteamrow, Ourteamcol의 주소를 참조
         }
      }
   }
 	return 0;
}

	
	void GetDefencePos(int board[][BOARD_SIZE], int* row, int* col){		//parameter에 각각 Boardweighting, Oppositerow, Oppositecol 
 
   int Max=0;
 
   for(int i=0;i< 15; i++){
      for(int j=0;j< 15; j++){
          
         if(board[i][j] > Max && board[i][j] < PLAYER){
            Max = board[i][j];
            *row =i;
            *col =j;		//Oppositerow, Oppositecol의 주소를 참조
         }
      }
   }	
	return 0;
}

	void CompareWeight(){
				if (Boardweighting[Ourteamrow][Ourteamcol]>Boardweighting[Oppositerow][Oppositecol]){		//우리팀 기준 가중치가 더 클때 그곳에 둔다
					x[i]=Ourteamcol;
					y[i]=Ourteamrow;
			
				}
		
				if (Boardweighting[Ourteamrow][Ourteamcol]<=Boardweighting[Oppositerow][Oppositecol]){		//상대팀 기준 가중치가 더 클때 혹은 같을 때 그곳에 둔다
					x[i]=Oppsitecol;
					y[i]=Oppositerow;
			
				}
	}

 

당시 상대가 놓은 돌의 위치별로 가중치를 둬서 가장 높은 가치함수를 가진 곳에 수를 두는 알고리즘을 만드려고 했었다.

 

이 점이 문제였던게, 바둑이 아닌 육목이더라도, 상대의 돌의 수에 대한 가지의 수가 생각보다 많기 때문에 이걸 모두 가지의 수를 나누어서 문제를 해결한다는 것은 의미가 없다. 뭐 그 이전에 이미 가중치를 두는 기준조차도 틀렸지만...


지금 다시 시도한다면 아마 이렇게 할 것 같다.

 

1. 강화학습을 적용한다.

 

우선 육목판의 배열에 대해서 모두 하나의 State로 지정해준다. 그리고 각 상태에 대응하는 Action의 배열을 만들어준다.

 

그리고 State에 대한 아주 기본적인 최고 순위 Action(상대가 막힌 곳이 없이 3줄이 나온 경우)등을 제일 먼저 체크한 후, 각 육목판 State에서 할 수 있는 Action을 랜덤하게 행하고, 승리시 그에 대한 가중치를 상승시킨다.

 

다만, 이 육목판에서 나올 수 있는 경우의 수가 최대$3^{19×19}$인데다가, 7초 시간제한이 있고, 사용 가능한 언어도 C/C++로 제한되어있으니, 이 모든 과정을 예선 전에 미리 해놓고 weigting module에서의 모든 액션 가중치를 만들어놔야 할 것이다.

 

또한 룰 중에 blocking에 대한 룰이 있으므로, 최대 승률을 가진 action이 실행 불가할 때 그 다음 최대 승률의 action을 행할 수 있도록 설정해놔야 할 것이다.

 

이 방법이 최선은 아니겠지만, 더 나은 방법이 생각이 나지 않는다.

 

2. 내가 모르는 것과 공부해야하는 것을 명확히 한다. 그리고 공부해야하는 것을 찾는 방법을 알아낸다.

 

사실 이게 제일 이 프로젝트를 하고 제일 크게 깨달은 것이었다. 저 당시 강화학습이나 API 사용하는 요령을 모르는 것보다는, 무엇을 알아야하는 지를 고민하지 않은 것이 제일 큰 문제였다. '그냥 생각하면 답 나오겠지...'하고 무작정 코딩해서 나온게 저런 쓰레기같은 코드다.

 

아마 다시 저 때로 돌아간다면 적어도 바둑이던 체스던, 게임에 대한 알고리즘을 찾아보고 벤치마킹하거나 강화학습에 대해서 찾아보고 구현하려고 시도해보지 않았을까 싶다.

 

3. 프로젝트 중 공부한 것에 연장해서 공부했을 것 같다.

 

이 프로젝트가 끝나고 나는 한동안 프로그래밍에 손을 대지 않았다. 5달 정도?

 

육목 프로젝트 한 번으로, 난 나의 문제점을 굉장히 많이 파악할 수 있었다. 코드 가독성, 알고리즘과 자료구조에 대한 이해 부족, 기계학습에 대한 기본적인 접근법 등... 사실 이 시기만큼 공부하기에 적합했던 때가 없었던 것 같다.

 

하지만 난 당시 내 실력을 부끄럽게 여기면서도, 발전하고자 노력하진 않았던 것 같다. 학과 공부네 동아리네 뭐네 바쁘긴 했지만, 학과 공부를 제외하면 다른 것들이 프로그래밍보다 중요했나 싶기도 하고... 지금 돌아보면 많이 후회된다.

 


당분간은 이렇게 전에 했었던 프로젝트를 돌아보면서 당시 부족했던 건 무엇이고, 지금도 그렇지 않은지, 앞으로 어떤 것을 배우는 게 좋을지 생각하는 글을 쓸까 한다.

 

2편에서 계속...

 

 

 

반응형