Перестановка карт в C#
Я пытаюсь написать код для проекта, который перечисляет содержимое колоды карт, спрашивает, сколько раз человек хочет перетасовать колоду, а затем перемешивает их. Он должен использовать метод для создания двух случайных целых чисел, используя класс System.Random.
Это мои занятия:
Program.cs:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Deck mydeck = new Deck();
foreach (Card c in mydeck.Cards)
{
Console.WriteLine(c);
}
Console.WriteLine("How Many Times Do You Want To Shuffle?");
}
}
}
Deck.cs:
namespace ConsoleApplication1
{
class Deck
{
Card[] cards = new Card[52];
string[] numbers = new string[] { "2", "3", "4", "5", "6", "7", "8", "9", "J", "Q", "K" };
public Deck()
{
int i = 0;
foreach(string s in numbers)
{
cards[i] = new Card(Suits.Clubs, s);
i++;
}
foreach (string s in numbers)
{
cards[i] = new Card(Suits.Spades, s);
i++;
}
foreach (string s in numbers)
{
cards[i] = new Card(Suits.Hearts, s);
i++;
}
foreach (string s in numbers)
{
cards[i] = new Card(Suits.Diamonds, s);
i++;
}
}
public Card[] Cards
{
get
{
return cards;
}
}
}
}
Enums.cs:
namespace ConsoleApplication1
{
enum Suits
{
Hearts,
Diamonds,
Spades,
Clubs
}
}
Card.cs:
namespace ConsoleApplication1
{
class Card
{
protected Suits suit;
protected string cardvalue;
public Card()
{
}
public Card(Suits suit2, string cardvalue2)
{
suit = suit2;
cardvalue = cardvalue2;
}
public override string ToString()
{
return string.Format("{0} of {1}", cardvalue, suit);
}
}
}
Пожалуйста, скажите мне, как сделать так, чтобы карты перемешивались столько, сколько хочет человек, а затем перечислите перемешанные карты.
10 ответов
Используйте тасовку Фишера-Йейтса.
Ваш код C# должен выглядеть примерно так:
static public class FisherYates
{
static Random r = new Random();
// Based on Java code from wikipedia:
// http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
static public void Shuffle(int[] deck)
{
for (int n = deck.Length - 1; n > 0; --n)
{
int k = r.Next(n+1);
int temp = deck[n];
deck[n] = deck[k];
deck[k] = temp;
}
}
}
Перетасовка колоды карт поначалу кажется тривиальной, но обычно алгоритм, который придумывает большинство людей, неверен.
Джефф Этвуд ( Coding Horror) написал несколько очень хороших статей на эту тему:
http://www.codinghorror.com/blog/archives/001008.html
http://www.codinghorror.com/blog/archives/001015.html
(особенно второй является обязательным для прочтения)
Я думаю, что это тот случай, когда вы просто слишком увлекаетесь абстракцией.
Перетасовка колоды карт в программном обеспечении - это вопрос предоставления колоды пользователю в случайном порядке. Это на самом деле не требует, чтобы вы перетасовали их заранее.
Начните свою колоду. (Я обычно использую число от 1 до 52, чтобы представить карту и математически вычислить, какая карта есть.)
- Сдайте карту, используя генератор случайных чисел, чтобы выбрать карту из колоды доступных карт.
- Поменяйте местами эту карту с той, которая находится в конце колоды.
- Уменьшите счетчик, указывающий на конец колоды, чтобы удалить эту карту из колоды.
- Переходите к шагу 1, пока не закончите рисовать карты.
Редактировать: И вообще, если у вас есть хороший генератор случайных чисел, ничего не получится, если "перемешать" его несколько раз.
Это должно быть возможно с использованием структур данных, которые вы показали. Вам просто нужно добавить метод Draw и переменную-член, чтобы отслеживать конец колоды. Если вы чертовски настроены на то, чтобы на самом деле выполнить "перемешивание" заблаговременно, то A ваш профессор - придурок, B каждый раз, когда вы берете 52 карты, колода будет перетасовываться. После того, как вы вытянули все карты, вам нужно предоставить метод "DeckEmpty" и метод для сброса конца колоды, чтобы снова включить все карты.
Чтобы правильно перетасовать колоду, вы НЕ должны использовать ТОЛЬКО класс Random, начальное число составляет всего 2^32, что означает, что ваш случайный объект может дать вам только 2^32 (предполагаемый) другой порядок там, где 52! (факториал 52) способ создания реальной колоды.
Я использую 2 Guid для создания 32 байтов случайных данных -> 8 семян 4 байта, и я перетасовываю карты с этими 8 различными семенами
затем по семени я получаю определенное количество карточек [5,5,6,6,6,7,8,9]
вот код, который я использую
public void Shuffle(Guid guid1, Guid guid2)
{
int[] cardsToGet = new int[] { 5, 5, 6, 6, 6, 7, 8, 9 };
byte[] b1 = guid1.ToByteArray();
byte[] b2 = guid2.ToByteArray();
byte[] all = new byte[b1.Length + b2.Length];
Array.Copy(b1, all, b1.Length);
Array.Copy(b2, 0, all, b1.Length, b2.Length);
List<Card> cards = new List<Card>(this);
Clear();
for (int c = 0; c < cardsToGet.Length; c++)
{
int seed = BitConverter.ToInt32(all, c * 4);
Random random = new Random(seed);
for (int d = 0; d < cardsToGet[c]; d++)
{
int index = random.Next(cards.Count);
Add(cards[index]);
cards.RemoveAt(index);
}
}
}
Ваш Shuffle может работать, но он не очень эффективен и не похож на жизнь. Вы должны попробовать этот способ:
//The shuffle goes like this: you take a portion of the deck, then put them in random places
private void Shuffle()
{
int length = DeckofCards.Count;
int level = 20; //number of shuffle iterations
List<Card> Shuffleing; //the part of the deck were putting back
Random rnd = new Random();
int PickedCount, BackPortion; //the last used random number
for (int _i = 0; _i < level; _i++)
{
PickedCount = rnd.Next(10, 30); //number of cards we pick out
Shuffleing = DeckofCards.GetRange(0, PickedCount);
DeckofCards.RemoveRange(0, PickedCount);
while (Shuffleing.Count != 0)
{
PickedCount = rnd.Next(10, DeckofCards.Count - 1); //where we place a range of cards
BackPortion = rnd.Next(1, Shuffleing.Count / 3 + 1); //the number of cards we but back in one step
DeckofCards.InsertRange(PickedCount, Shuffleing.GetRange(0, BackPortion)); //instering a range of cards
Shuffleing.RemoveRange(0, BackPortion); //we remove what we just placed back
}
}
}
Таким образом, вы можете получить более реалистичное перемешивание с меньшим количеством итераций
Используйте Enum в своем классе карты.
public class Card
{
public Suit Suit { get; set; }
public CardValue CardValue { get; set; }
}
public enum CardValue
{
Ace = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13
}
public enum Suit
{
Clubs,
Hearts,
Spades,
Dimonds
}
Как только вы это сделаете, вы можете создать класс PlayersHand
public class PlayersHand
{
public List<Card> Cards { get; set; }
}
Теперь, когда у вас есть оба класса, созданные и поддерживаемые Enum, просто дайте каждому игроку руку из колоды, которая представляет собой просто список карт.
List<Card> deck = new List<Card>();
// 52 cards in a deck, 13 cards per suit.
foreach (Suit suit in Enum.GetValues(typeof(Suit)))
{
foreach (CardValue cardValue in Enum.GetValues(typeof(CardValue)))
{
Card card = new Card
{
Suit = suit,
CardValue = cardValue
};
deck.Add(card);
}
}
// Shuffle using random replace.
for (var i = deck.Count-1; i > 0; i--)
{
Random rnd = new Random();
var j = rnd.Next(0, i);
var temp = deck[i];
deck[i] = deck[j];
deck[j] = temp;
}
// THESE ARE THE SETTINGS
int NumberOfPlayers = 4;
int NumberOfCardsPerPlayer = 5;
// NEW UP A LIST OF PLAYER HANDS
List<PlayersHand> hands = new List<PlayersHand>();
for (var i = 1; i <= NumberOfPlayers; i++)
{
PlayersHand newHand = new PlayersHand()
{
Cards = new List<Card>()
};
hands.Add(newHand);
}
// HERE IS THE TRICK TO DEALING OUT THE TOP CARD
int deal = 0;
// GOING AROUND THE TABLE 5 TIMES BECAUSE EACH PLAYER HAS 5 CARDS
for (var i = 0; i < NumberOfCardsPerPlayer ; i++)
{
// FOR EACH PLAYER GET THE TOP CARD USING THE 'DEAL' INT
for (var p = 0; p < NumberOfPlayers; p++)
{
var player = hands[p];
player.Cards.Add(deck[deal]);
// INCREMENT DEAL
deal++;
}
}
У меня такой подход работает.
Перемешивание должно работать следующим образом:
Вы берете две случайные карты в колоде (индекс карты в колоде - случайные числа) и меняете местами две карты. Например, возьмите карточку с индексом 2 и карточку с индексом 9 и попросите их поменять место.
И это может повторяться определенное количество раз.
Алгоритм должен выглядеть примерно так:
int firstNum = rnd.Next(52);
int secondNum = rnd.Next(52);
Card tempCard = MyCards[firstNum];
MyCards[firstNum] = MyCards[secondNum];
MyCards[secondNum] = tempCard;
static void Shuffle(List<int> cards)
{
Console.WriteLine("");
Console.WriteLine("Shuffling");
Console.WriteLine("---------");
cards = cards.OrderBy(x => Guid.NewGuid()).ToList();
foreach (var card in cards)
{
Console.WriteLine(card.ToString());
}
}
Я сделал программу, которая содержит 7 карт, затем перемешать, и я надеюсь взять, чтобы помочь им.
Программа класса {
static void Main(string[] args)
{
Random random = new Random();
var cards = new List<string>();
//CARDS VECRTOR
String[] listas = new String[] { "Card 1", "Card 2", "Card 3", "Card 4", "Card 5", "Card 6", "Card 7"};
for (int i = 0; i<= cards.Count; i++)
{
int number = random.Next(0, 7); //Random number 0--->7
for (int j = 0; j <=6; j++)
{
if (cards.Contains(listas[number])) // NO REPEAT SHUFFLE
{
number = random.Next(0, 7); //AGAIN RANDOM
}
else
{
cards.Add(listas[number]); //ADD CARD
}
}
}
Console.WriteLine(" LIST CARDS");
foreach (var card in cards)
{
Console.Write(card + " ,");
}
Console.WriteLine("Total Cards: "+cards.Count);
//REMOVE
for (int k = 0; k <=6; k++)
{
// salmons.RemoveAt(k);
Console.WriteLine("I take the card: "+cards.ElementAt(k));
cards.RemoveAt(k); //REMOVE CARD
cards.Insert(k,"Card Taken"); //REPLACE INDEX
foreach (var card in cards)
{
Console.Write(card + " " + "\n");
}
}
Console.Read(); //just pause
}
}
В целом, я бы сказал, что каждая колода рассматривается как объект, который содержит массив объектов Card, каждый из которых содержит свойство value и suite int, которые могут применяться к Enum значений и наборов для сбора именованной версии как в соответствии с типом колоды, которую вы используете. (Это позволит сделать этот фрагмент кода более универсальным и упростить сравнение значений. 3 < 11 (Джек)!~) Ваш стиль будет работать для школьного проекта, я просто получаю ОКР!
class Card
{
public int value
{ get; set; }
public int suite
{ get; set; }
}
abstract class Deck
{
public Card[] cards
{ get; set; }
public void ShuffleCards(int timesToShuffle)
{
Card temp;
Random random = new Random();
// int timesToShuffle = random.Next(300, 600); #Had it setup for random shuffle
int cardToShuffle1, cardToShuffle2;
for (int x = 0; x < timesToShuffle; x++)
{
cardToShuffle1 = random.Next(this.cards.Length);
cardToShuffle2 = random.Next(this.cards.Length);
temp = this.cards[cardToShuffle1];
this.cards[cardToShuffle1] = this.cards[cardToShuffle2];
this.cards[cardToShuffle2] = temp;
}
}
}
Это предполагает, что вы использовали базовый класс Deck, а затем наследуете его в соответствии с тем типом колоды, который вам нужен (делая так, чтобы вы могли применять этот же код к декам Uno или как угодно.) Код для нормального типа класса колоды.
class NormalDeck : Deck
{
// This would go in the NormalGame class to apply the enumerators to the values as a cipher.
// Need int values for logic reasons (easier to work with numbers than J or K !!!
// Also allows for most other methods to work with other deck<Type> (ex: Uno, Go Fish, Normal cards)
public enum Suites
{
Hearts,
Diamonds,
Spades,
Clover
};
// Same comment as above.
public enum Values
{ Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King };
public void NewNormalDeck()
{
// Clear the deck of cards
if (this.cards != null)
{
Array.Clear(this.cards, 0, this.cards.Length);
}
//Set Value to length of Normal deck of Cards without Jokers
cards = new Card[52];
// to keep count of which card we are.
int curNumofCards = 0;
// Cycle through all of the suites listed in "suites" then all the values of that suite
for (int x = 0; x < Enum.GetValues(typeof(Suites)).GetLength(0); x++)
{
for (int y = 0; y < Enum.GetValues(typeof(Values)).GetLength(0); y++)
{
Card newCard = new Card();
newCard.suite = x;
newCard.value = y;
this.cards[curNumofCards] = newCard;
curNumofCards++;
}
}
}
}