[CodinGame] WAR(카드 게임 알고리즘-Queue)
이번 게임은 War이라는 오직 운빨만으로 승자가 결정되는 카드 게임입니다.
[Rules_규칙]
목표: 현재 카드 상황을 보고 승자가 누구인지 판별하는 프로그램
규칙:
- 두 명의 플레이어 사이에서 진행되는 카드 게임입니다.
- 각 플레이어는 시작 시 덱의 변수 수의 카드를 받습니다.
- 각 라운드마다, 각 플레이어는 덱의 맨 위 카드를 뒤집어 보여주며, 숫자가 높은 쪽이 그 라운드의 승자가 됩니다. 이것을 "결투"라고 합니다.
- 만약 두 카드의 숫자가 같다면, "전쟁"이 발생합니다.
- "전쟁"에서는 각 플레이어가 덱에서 다음 세 장의 카드를 뒤집어 놓습니다.
- 이후 다시 승자를 가리기 위해 라운드를 다시 시작합니다.
- 한 플레이어가 "전쟁" 도중 카드가 부족해지면, 게임은 끝나며 무승부가 됩니다.
- 각 카드는 값과 모양으로 표시됩니다. 하지만 값만으로 승패를 따집니다.
- 한 플레이어가 승리하면 승자의 덱의 바닥으로 카드를 다시 돌려놓습니다.
[문제]
https://www.codingame.com/training/medium/winamax-battle
Practice Queues with the exercise "War"
Want to practice coding? Try to solve this puzzle "War" (25+ languages supported).
www.codingame.com
[난이도]
[학습 개념]
[풀이]
1. 카드의 순서가 중요하고 카드가 항상 들어온 순서대로 나가기 때문에 큐를 써서 플레이어 덱을 넣을 생각으로 큐 변수를 생성했습니다.
2. 큐에 입력받은 카드 덱들을 넣었습니다.
3. 규칙에 보면 결투를 하고 전쟁을 합니다. 그리고 전쟁 중에도 결투를 합니다. 그렇기에 함수로 나눠서 만들어야겠다고 생각하게 되었습니다.
4. 먼저 Fight 함수를 만들고 규칙에 따라 각 플레이어가 카드 한 장씩 뽑게 각각의 카드덱에서 Dequeue()를 써서 string 변수에 담았습니다.
5. 이제 카드의 등급을 어떻게 구별해서 비교할지에 대해 고민했습니다. 처음에는 string rank = "123456789"+"10"+"JQKA" 이렇게 해서 IndexOf() 함수로 인덱스의 값으로 하려고 했지만 두 카드의 등급 받아 비교하는 함수와 카드의 등급만을 알려주는 함수로 나뉘어야 더 알고리즘 풀이의 기본 개념인 분할 정복법에 맞고 이렇게 하면 한눈에 코드의 흐름을 알 수 있어 지금과 같은 CompareCards 함수와 GetCardRank 함수 두 개를 만들었습니다.
6. 이제 카드의 등급을 비교할 수 있으니 아까 뽑은 두 카드를 비교해서 이긴 사람이 카드를 다 가져가게 코드를 짯습니다.
7. 그리고 카드의 등급이 같다면 전쟁을 할 수 있도록 War 함수를 만들었습니다.
8. 그전에 결투에서 썼던 카드를 임시 전쟁 덱에 넣고 War 함수를 호출했습니다.
9. 그 후 전쟁 중 뽑는 카드를 저장할 임시 전쟁 덱을 생성했습니다.
10. 3장의 카드를 뽑아 그 카드에 넣는 코드를 추가하고 그 사이에 카드의 숫자가 부족하면 무승부가 되어 프로그램이 종료되도록 했습니다.
11. 그 후 다시 결투를 하기 위해 카드 한 장을 뽑아 결투를 하기 위해 Fight 함수를 써야 했습니다.
12. 하지만 전쟁에서 결투를 위해 카드를 뽑으면 Fight 함수에서도 카드를 뽑기 때문에 이중 뽑기가 되기 때문에 Peek() 함수를 써서 카드의 정보만 임시 전쟁 덱에 저장하였습니다.
13. 결투를 벌이고 이긴 승자에게 임시 전쟁 덱을 몰아넣어 줘야 했기 때문에 승자를 알기 위해 Fight 함수를 int를 반환하도록 수정하고 플레이어 1이 승리하면 1을 반환하고 플레이어 2가 승리하면 2를 반환하고, 무승부일 경우 0을 반환하도록 했습니다.
14. 그 후 이긴 승자에게 카드를 몰아넣어줬습니다. 그런데 이 결투에서 승부가 또 안 난다면 전쟁을 한 번 더 하도록 했습니다.
15. 여기까지 하고 실행했었을 때 당연히 안됐습니다. (지금 풀이 쓸 때는 당연한데 할 땐 일단 안되니까 당황 후 침착하게 "뭐지 왜 안돼" 하고 문제를 찾아봤습니다.)
16. 답은 War 함수 내에서 Fight 함수를 써서 기대하는 기능에 더해 12번 풀이 과정에서 설명한 것과 같이 카드 하나를 뽑고 그 카드를 카드덱에 넣어버리는 문제가 있었습니다.
17. 우리는 뽑아서 카드 한 장을 없애는 기능과 승자를 비교해서 승자를 반환하는 기능만 원하기 때문에 이 기능을 제외한 부분에 조건문을 달아서 War 함수에서는 쓸 수 없도록 막았습니다.
18. 그 후 테스트 케이스를 돌려보고 풀이가 완료되었습니다.
[소스 코드]
using System;
using System.Collections.Generic;
class Solution
{
static Queue<string> player1Deck = new Queue<string>(); // 1번 플레이어 카드 덱
static Queue<string> player2Deck = new Queue<string>(); // 2번 플레이어 카드 덱
static Queue<string> temporary_warDeck1 = new Queue<string>(); // 임시 전쟁 덱 1
static Queue<string> temporary_warDeck2 = new Queue<string>(); // 임시 전쟁 덱 2
static int gameturn = 0; // 게임 턴 수
static void Main(string[] args)
{
// 입력 처리
int n = int.Parse(Console.ReadLine());
for (int i = 0; i < n; i++)
{
string card = Console.ReadLine();
player1Deck.Enqueue(card);
}
int m = int.Parse(Console.ReadLine());
for (int i = 0; i < m; i++)
{
string card = Console.ReadLine();
player2Deck.Enqueue(card);
}
// 게임 진행
while (player1Deck.Count > 0 && player2Deck.Count > 0)
{
++gameturn;
Fight();
// 게임 진행 상황 출력
#region Check the progress of the card game
Console.Error.WriteLine($"{gameturn}번째 턴");
Console.Error.Write($"player1Deck: ");
foreach (string card in player1Deck)
{
Console.Error.Write($"{card} ");
}
Console.Error.Write($"\nplayer2Deck: ");
foreach (string card in player2Deck)
{
Console.Error.Write($"{card} ");
}
Console.Error.Write($"\n\n\n");
#endregion
}
// 결과 출력
Console.WriteLine(player1Deck.Count > 0 ? $"1 {gameturn}" : $"2 {gameturn}");
}
static int Fight(string IsWarFun = "Fight")
{
// 각 플레이어가 카드 한 장씩 뽑음
string player1Card = player1Deck.Dequeue();
string player2Card = player2Deck.Dequeue();
// CompareCards 함수를 사용하여 카드 랭크를 비교하고
// player1이 이길 경우 player1Deck의 뒤에 두 카드를 추가하고 1을 반환합니다.
if (CompareCards(player1Card, player2Card) > 0)
{
if (IsWarFun == "Fight")
{
player1Deck.Enqueue(player1Card);
player1Deck.Enqueue(player2Card);
}
return 1;
}
// player2가 이길 경우 player2Deck의 뒤에 두 카드를 추가하고 2를 반환합니다.
else if (CompareCards(player1Card, player2Card) < 0)
{
if (IsWarFun == "Fight")
{
player2Deck.Enqueue(player1Card);
player2Deck.Enqueue(player2Card);
}
return 2;
}
else // 무승부일 경우
{
if (IsWarFun == "Fight")
{
// 임시 전쟁 덱에 카드 1장씩 추가
temporary_warDeck1.Enqueue(player1Card);
temporary_warDeck2.Enqueue(player2Card);
// 전쟁 단계 실행
War(player1Card, player2Card);
}
return 0;
}
}
static void War(string player1Card, string player2Card)
{
// 3장의 카드를 뽑아 내려놓습니다.
// (나중에 다시 이긴 사람의 덱에 들어가니 임시 전쟁덱에 넣습니다.)
for (int i = 0; i < 3; i++)
{
// 플레이어 1 혹은 플레이어 2의 카드가 2장 이하로 남았으면 비깁니다.
if (player1Deck.Count < 2 || player2Deck.Count < 2)
{
Console.WriteLine("PAT");
Environment.Exit(0);
}
temporary_warDeck1.Enqueue(player1Deck.Dequeue());
temporary_warDeck2.Enqueue(player2Deck.Dequeue());
}
// 결투를 위해 카드 한장을 더 뽑음(실제로 뽑는건 곁투에서 하니까 여긴 Peek())
string card1 = player1Deck.Peek();
string card2 = player2Deck.Peek();
//뽑은 카드 정보를 임시 전쟁카드덱에 저장
temporary_warDeck1.Enqueue(card1);
temporary_warDeck2.Enqueue(card2);
//결투를 벌입니다. 결과가 나오면 승자에게 사용한 카드를 몰아 줍니다.
int winPlayer = Fight("War");
if (winPlayer == 1)
{
int tempCount = temporary_warDeck1.Count;
for (int i = 0; i < tempCount; i++)
{
player1Deck.Enqueue(temporary_warDeck1.Dequeue());
}
tempCount = temporary_warDeck2.Count;
for (int i = 0; i < tempCount; i++)
{
player1Deck.Enqueue(temporary_warDeck2.Dequeue());
}
}
else if (winPlayer == 2)
{
int tempCount = temporary_warDeck1.Count;
for (int i = 0; i < tempCount; i++)
{
player2Deck.Enqueue(temporary_warDeck1.Dequeue());
}
tempCount = temporary_warDeck2.Count;
for (int i = 0; i < tempCount; i++)
{
player2Deck.Enqueue(temporary_warDeck2.Dequeue());
}
}
else // 다시 한 번 무승부가 되면 재귀적으로 War 함수를 호출합니다
{
War(card1, card2);
}
}
//두 카드의 등급을 비교합니다.
static int CompareCards(string card1, string card2)
{
int card1Rank = GetCardRank(card1);
int card2Rank = GetCardRank(card2);
return card1Rank - card2Rank;
}
//카드의 등급을 반환합니다.
static int GetCardRank(string card)
{
card = card.Substring(0, card.Length - 1);
switch (card)
{
case "J":
return 11;
case "Q":
return 12;
case "K":
return 13;
case "A":
return 14;
default:
return int.Parse(card);
}
}
}
[아쉬웠던 점]
규칙을 이해하는 부분과 그 규칙을 코드에 정확히 적용시키는 능력이 부족하여 풀이에 1주일이라는 시간이 걸린 부분이 아쉬웠습니다. 그래도 오로지 스스로의 힘으로 결국 다 풀어 성취욕이 엄청나서 좋았습니다.
[새롭게 알게 된 점]
1. 숫자와 문자가 혼합된 카드의 등급을 나눌 때 어떻게 할지 몰랐는데 stackoverflow에서 IndexOf를 쓰는 방법과 swich를 쓰는 방법을 알게 되었습니다.
2. 스택과 큐에 대해 이해가 깊어졌습니다.
3. 전역 변수와 정적 함수 사용 그리고 재귀 함수 사용법에 대한 이해가 깊어졌습니다.
4. 디버그를 통해 코드의 흐름에 중복된 문자열이 한번 더 들어가는 걸 발견해서 디버깅를 더 생활화 하게 되었습니다.