{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Shuffle and Deal with Arrays\n", "\n", "Arrays are the simplest way to collect data in C#. In this lesson we're going to talk about how we can use an array to represent the deck of cards in a game. Let's face it, a game of cards without a deck of cards to find is kind of boring.\n", "\n", "## What is an Array?\n", "\n", "In C#, an array is a data structure that holds a fixed number of elements of a single type. The size of the array is determined at the time of creation and cannot be changed afterwards. Each element in the array can be accessed by its index, which starts from 0 and goes up to one less than the total size of the array.\n", "\n", "We can declare an array with a type and a pair of square brackets. We then can intialize the arrays with a count inside the brackets to define the size of the array. \n", "\n", "Let's define an array of 52 strings for the deck of cards and 2 additional arrays of 5 strings for the hands of cards for two players" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "string[] deckOfCards = new string[52];\n", "string[] myHand = new string[5];\n", "string[] yourHand = new string[5];" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's initialize the two hands with a series of cards. We can do this by including after the new string initializer inside curly braces the values that we want to assign to each hand. There's also a newer syntax called **collection expressions** where we can just define our strings inside of square brackets directly. We can use these two ways to declare our starting hands:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "string[] myHand = new string[5] {\"2♥️\", \"3♦️\", \"4♠️\", \"5♣️\", \"6♥️\"};\n", "string[] yourHand = [\"2♠️\", \"3♠️\", \"4♠️\", \"5♠️\", \"6♠️\"];\n", "\n", "display(myHand);\n", "display(yourHand);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can then reference individual elements inside the array by using the variable name Square brackets and then the zero based index of the element. If I wanted to grab that three of diamonds out of my hand I can use this syntax to access it:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "display(myHand[1]);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also swap the value that's in the array at a specific index with a simple equals operator. Perhaps I'm going to give up and trade in that three of diamonds and try and grab another card from the deck. Let's assign an ace of spades in place of the string at index 1 in my hand:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "myHand[1] = \"A♠️\";\n", "display(myHand);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember this is a fixed number of entries in the array. That means that we can't reference and interact with elements that are in an index beyond the number that we have allocated. Can't access a negative entry in the array and you cannot access an entry in the array that's more than we've allocated. The following statement will reflect an `IndexOutOfRange` error:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "display(myHand[20])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can check the length of the array by using the length property and .NET will return the number of entries that are in the array. This is different from the largest index in the array. Remember arrays start with an index of 0 so the length of the array will actually be the highest index in the array plus one" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "display(myHand.Length);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multidimensional Arrays\n", "\n", "In C#, a multidimensional array is an array with more than one dimension. You might be familiar with two-dimensional arrays (also known as matrix) which include rows and columns. However, C# supports arrays with even more dimensions.\n", "\n", "### Declaration and Initialization\n", "\n", "A multidimensional array is declared by specifying the number of dimensions in square brackets. Here's how you can declare a two-dimensional array:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "int[,] array2D = new int[3, 4];" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This creates a 2D array with 3 rows and 4 columns. You can also initialize the array at the time of declaration:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "int[,] array2D = new int[,] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Accessing Elements\n", "\n", "You can access elements in a multidimensional array by specifying indices for each dimension. For example, to access the element in the second row and third column of array2D, you would write:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "display(array2D[1, 2]);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember, array indices in C# start from 0, so array2D[1, 2] refers to the second row and third column.\n", "\n", "## Assigning the cards of the deck with a For loop\n", "\n", "In our [last lesson](csharpinthecards.com/lesson/8) we learned about writing loops to perform repeated tasks. Let's write some loops to generate the full deck of cards. We'll start with a pair of arrays, one for the suits and another for the ranks. We'll merge those values and assign the 52 string values for the standard poker deck of cards:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "string[] cards = new string[52];\n", "string[] suits = { \"♠\", \"♥\", \"♦\", \"♣\" };\n", "string[] ranks = { \"A\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"T\", \"J\", \"Q\", \"K\" };\n", "\n", "display(suits);\n", "display(ranks);\n", "\n", "for (int i = 0; i < cards.Length; i++)\n", "{\n", "\t\tcards[i] = ranks[i % 13] + suits[i / 13];\n", "}\n", "\n", "display(cards);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we're talking! We've just generated a deck of cards with 5 lines of code. \n", "\n", "But... what good is a deck of cards if we can't shuffle it? \n", "\n", "## Shuffling an array of cards with Fisher-Yates\n", "\n", "We can use the [Fisher-Yates shuffle algorithm](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) to scramble the cards in our deck array. It’s an algorithm for generating a random permutation of a finite sequence, and in our case that sequence is the 52 cards. The algorithm continually determines the next element in the shuffled sequence by randomly drawing an element from the list until no elements remain. It’s also known as the Knuth shuffle.\n", "\n", "To use this algorithm, we'll introduce the `Random` feature in .NET. We can create a random-number generator and request an integer value of a specific length using the following syntax. Go ahead and execute the following code several times to see what number it generates." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "var random = new Random();\n", "display(random.Next(52));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we know how to choose a random number, we can apply the Fisher-Yates shuffle with this syntax that should appear familiar now. It's a for-loop that inspects each card and swaps it's location with a card located in a random location elsewhere in the deck. Try executing this code several times to see what the shuffled deck looks like." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "// Initialize the deck\n", "string[] cards = new string[52];\n", "string[] suits = { \"♠\", \"♥\", \"♦\", \"♣\" };\n", "string[] ranks = { \"A\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"T\", \"J\", \"Q\", \"K\" };\n", "for (int i = 0; i < cards.Length; i++)\n", "{\n", "\t\tcards[i] = ranks[i % 13] + suits[i % 4];\n", "}\n", "\n", "// Shuffle the deck using the Fisher-Yates algorithm\n", "var random = new Random();\n", "for (int i = 0; i < cards.Length; i++)\n", "{\n", "\t\tint j = random.Next(52);\n", "\t\tstring temp = cards[i];\n", "\t\tcards[i] = cards[j];\n", "\t\tcards[j] = temp;\n", "}\n", "\n", "display(cards);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Challenge:** maybe your opponent doesn't think that shuffling the cards once is random enough. Can you shuffle the cards 5 times, shuffling a shuffled deck and produce a sequence of cards that is more random?\n", "\n", "Does that make the deck more random? Not sure...\n", "\n", "Let's deal five cards to you and I for a nice game of poker:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "// Initialize the deck\n", "string[] cards = new string[52];\n", "string[] suits = { \"♠\", \"♥\", \"♦\", \"♣\" };\n", "string[] ranks = { \"A\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"T\", \"J\", \"Q\", \"K\" };\n", "for (int i = 0; i < cards.Length; i++)\n", "{\n", "\t\tcards[i] = ranks[i % 13] + suits[i % 4];\n", "}\n", "\n", "// Shuffle the deck using the Fisher-Yates algorithm\n", "var random = new Random();\n", "\n", "for (int i = 0; i < cards.Length; i++)\n", "{\n", "\t\tint j = random.Next(52);\n", "\t\tstring temp = cards[i];\n", "\t\tcards[i] = cards[j];\n", "\t\tcards[j] = temp;\n", "}\n", "\n", "// Deal 5 cards from the shuffled array to you and I.\n", "for (int i = 0; i < 5; i++)\n", "{\n", "\t\tmyHand[i] = cards[i*2]; // Add the card to the hand\n", "\t\tcards[i*2] = null; // Remove the card from the array\n", "\n", "\t\tyourHand[i] = cards[i*2+1]; // Add the card to the hand\n", "\t\tcards[i*2+1] = null; // Remove the card from the array\n", "}\n", "\n", "// Display the hands of cards\n", "display(\"MyHand: \");\n", "display(myHand);\n", "\n", "display(\"YourHand: \");\n", "display(yourHand);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've just wrapped up a deep dive into C# arrays, specifically looking at how we can use them to represent a deck of cards. We've seen how arrays can hold our cards and how we can shuffle them around. And speaking of shuffling, we've also tackled the Fisher-Yates shuffle algorithm - a cool way to mix up our deck without any bias. So now, you're all set to use arrays and the Fisher-Yates shuffle in your C# projects. Keep coding, keep shuffling, and keep having fun!" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (C#)", "language": "C#", "name": ".net-csharp" }, "language_info": { "name": "polyglot-notebook" }, "polyglot_notebook": { "kernelInfo": { "defaultKernelName": "csharp", "items": [ { "aliases": [], "languageName": "csharp", "name": "csharp" } ] } } }, "nbformat": 4, "nbformat_minor": 2 }