Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C# to random number generation page (need help) #4556

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 239 additions & 13 deletions tutorials/math/random_number_generation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,53 @@ In global scope, you can find a :ref:`randomize()
once when your project starts to initialize the random seed.** Calling it
multiple times is unnecessary and may impact performance negatively.

Putting it in your main scene script's ``_ready()`` method is a good choice::
Putting it in your main scene script's ``_ready()`` method is a good choice:

.. tabs::
.. code-tab:: gdscript GDScript

func _ready():
randomize()

.. code-tab:: csharp

public override void _Ready()
{
GD.Randomize();
}


You can also set a fixed random seed instead using :ref:`seed()
<class_@GDScript_method_seed>`. Doing so will give you *deterministic* results
across runs::
across runs:

.. tabs::
.. code-tab:: gdscript GDScript

func _ready():
seed(12345)
# To use a string as a seed, you can hash it to a number.
seed("Hello world".hash())

.. code-tab:: csharp

// To use a string as a seed, you can hash it to a number.
GD.Seed((ulong)"Hello world".Hash());

When using the RandomNumberGenerator class, you should call ``randomize()`` on
the instance since it has its own seed::
the instance since it has its own seed:

.. tabs::
.. code-tab:: gdscript GDScript

var rng = RandomNumberGenerator.new()
rng.randomize()

.. code-tab:: csharp

var rng = new RandomNumberGenerator();
rng.Randomize();

Getting a random number
-----------------------

Expand All @@ -71,14 +98,25 @@ random numbers in Godot.
The function :ref:`randi() <class_@GDScript_method_randi>` returns a random
number between 0 and 2^32-1. Since the maximum value is huge, you most likely
want to use the modulo operator (``%``) to bound the result between 0 and the
denominator::
denominator:

.. tabs::
.. code-tab:: gdscript GDScript

# Prints a random integer between 0 and 49.
print(randi() % 50)

# Prints a random integer between 10 and 60.
print(randi() % 51 + 10)

.. code-tab:: csharp

// Prints a random integer between 0 and 49.
GD.Print(GD.Randi() % 50);

// Prints a random integer between 10 and 60
GD.Print(GD.Randi() % 51 + 10);

:ref:`randf() <class_@GDScript_method_randf>` returns a random floating-point
number between 0 and 1. This is useful to implement a
:ref:`doc_random_number_generation_weighted_random_probability` system, among
Expand All @@ -89,33 +127,64 @@ floating-point number between 0 and 1. Unlike :ref:`randf()
<class_@GDScript_method_randf>` which follows an uniform distribution, the
returned number follows a `normal distribution
<https://en.wikipedia.org/wiki/Normal_distribution>`__. This means the returned
value is more likely to be around 0.5 compared to the extreme bounds (0 and 1)::
value is more likely to be around 0.5 compared to the extreme bounds (0 and 1):

.. tabs::
.. code-tab:: gdscript GDScript

# Prints a normally distributed floating-point number between 0.0 and 1.0.
var rng = RandomNumberGenerator.new()
rng.randomize()
print(rng.randfn())

.. code-tab:: csharp

// Prints a normally distributed floating-point number between 0.0 and 1.0.
var rng = new RandomNumberGenerator();
rng.Randomize();
GD.Print(rng.Randfn());

:ref:`rand_range() <class_@GDScript_method_rand_range>` takes two arguments
``from`` and ``to``, and returns a random floating-point number between ``from``
and ``to``::
and ``to``:

.. tabs::
.. code-tab:: gdscript GDScript

# Prints a random floating-point number between -4 and 6.5.
print(rand_range(-4, 6.5))

.. code-tab:: csharp

// Prints a random floating-point number between -4 and 6.5.
GD.Print(GD.RandRange(-4, 6.5));

:ref:`RandomNumberGenerator.randi_range()
<class_RandomNumberGenerator_method_randi_range>` takes two arguments ``from``
and ``to``, and returns a random integer between ``from`` and ``to``::
and ``to``, and returns a random integer between ``from`` and ``to``:

.. tabs::
.. code-tab:: gdscript GDScript

# Prints a random floating-point number between -10 and 10.
var rng = RandomNumberGenerator.new()
rng.randomize()
print(rng.randi_range(-10, 10))

.. code-tab:: csharp

// Prints a random floating-point number between -10 and 10.
var rng = new RandomNumberGenerator();
rng.Randomize();
GD.Print(rng.RandiRange(-10, 10));

Get a random array element
--------------------------

We can use random integer generation to get a random element from an array::
We can use random integer generation to get a random element from an array:

.. tabs::
.. code-tab:: gdscript GDScript

var fruits = ["apple", "orange", "pear", "banana"]

Expand All @@ -135,8 +204,34 @@ We can use random integer generation to get a random element from an array::
# We may get the same fruit multiple times in a row.
return random_fruit

.. code-tab:: csharp

string[] fruits = {"apple", "orange", "pear", "banana"};

public override void _Ready()
{
GD.Randomize();

for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(get_fruit());
}
}

public string get_fruit()
{
string random_fruit = fruits[GD.Randi() % fruits.Length];
// Returns "apple", "orange", "pear", or "banana" every time the code runs.
// We may get the same fruit multiple times in a row.
return random_fruit;
}

To prevent the same fruit from being picked more than once in a row, we can add
more logic to this method::
more logic to this method:

.. tabs::
.. code-tab:: gdscript GDScript

var fruits = ["apple", "orange", "pear", "banana"]
var last_fruit = ""
Expand Down Expand Up @@ -166,6 +261,38 @@ more logic to this method::
# The function will never return the same fruit more than once in a row.
return random_fruit

.. code-tab:: csharp

string[] fruits = {"apple", "orange", "pear", "banana"};
public string lastFruit = "";

public override void _Ready()
{
GD.Randomize();

for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(get_fruit());
}
}

public string GetFruit()
{
string RandomFruit = fruits[GD.Randi() % fruits.Length];
while (RandomFruit == LastFruit)
{
// The last fruit was picked, try again until we get a different fruit.
random_fruit = fruits[GD.Randi() % fruits.Length];
}

LastFruit = RandomFruit;

// Returns "apple", "orange", "pear", or "banana" every time the code runs
// The function will never return the same fruit more than once in a row.
return random_fruit;
}

This approach can be useful to make random number generation feel less
repetitive. Still, it doesn't prevent results from "ping-ponging" between a
limited set of values. To prevent this, use the :ref:`shuffle bag
Expand All @@ -174,7 +301,10 @@ limited set of values. To prevent this, use the :ref:`shuffle bag
Get a random dictionary value
-----------------------------

We can apply similar logic from arrays to dictionaries as well::
We can apply similar logic from arrays to dictionaries as well:

.. tabs::
.. code-tab:: gdscript GDScript

var metals = {
"copper": {"quantity": 50, "price": 50},
Expand All @@ -196,6 +326,41 @@ We can apply similar logic from arrays to dictionaries as well::
# The same metal may be selected multiple times in succession.
return random_metal

.. code-tab:: csharp

struct MetalInfo
{
public MetalInfo(int quantity, int price)
{
this.quantity = quantity;
this.price = price;
}
public int quantity;
public int price;
}

enum MetalType
{
copper,
silver,
gold,
}
Comment on lines +344 to +347
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
copper,
silver,
gold,
}
Copper,
Silver,
Gold,
}

Enum members are PascalCase in C#, and there should be an extra line after the closing brace in this case.

However, there is a bigger problem: This code is too different from the GDScript version. Either the C# code should be more like the GDScript code, or the GDScript code should be more like the C# code, or both. The GDScript code is not using enums, the C# code is. The C# code is using a struct, which is not possible in GDScript so this part should be changed to be more like the GDScript code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how I was told on discord to make the dictionary in C#. If you know how to make the dictionary in a way that's more similar to GDScript show me and I'll add it.

Godot.Collections.Dictionary<MetalType, MetalInfo> metals = new Godot.Collections.Dictionary<MetalType, MetalInfo>
{
{ MetalType.copper, new MetalInfo(50, 50) },
{ MetalType.silver, new MetalInfo(20, 150) },
{ MetalType.gold, new MetalInfo(3, 500) }
};

public override void _Ready()
{
GD.Randomize();
for (int i = 0; i < 20; i++)
{
GD.Print(GetMetal());
}
}


.. _doc_random_number_generation_weighted_random_probability:

Expand All @@ -204,7 +369,10 @@ Weighted random probability

The :ref:`randf() <class_@GDScript_method_randf>` method returns a
floating-point number between 0.0 and 1.0. We can use this to create a
"weighted" probability where different outcomes have different likelihoods::
"weighted" probability where different outcomes have different likelihoods:

.. tabs::
.. code-tab:: gdscript GDScript

func _ready():
randomize()
Expand All @@ -226,6 +394,38 @@ floating-point number between 0.0 and 1.0. We can use this to create a
# 5% chance of being returned.
return "Rare"

.. code-tab:: csharp

public override void _Ready()
{
GD.Randomize();

for (int i = 0; i < 100; i++)
{
GD.Print(GetItemRarity());
}
}

public string GetItemRarity()
{
float RandomFloat = GD.Randf();

if (RandomFloat < 0.8)
{
// 80% chance of being returned
return "common";
}
if (RandomFloat > 0.95)
{
// 15% change of being returned
return "Uncommon";
}
else
{
// 5% chance of being returned
return "rare";
}

.. _doc_random_number_generation_shuffle_bags:

"Better" randomness using shuffle bags
Expand All @@ -238,7 +438,10 @@ could get the same fruit three or more times in a row.

You can accomplish this using the *shuffle bag* pattern. It works by removing an
element from the array after choosing it. After multiple selections, the array
ends up empty. When that happens, you reinitialize it to its default value::
ends up empty. When that happens, you reinitialize it to its default value:

.. tabs::
.. code-tab:: gdscript GDScript

var fruits = ["apple", "orange", "pear", "banana"]
# A copy of the fruits array so we can restore the original value into `fruits`.
Expand Down Expand Up @@ -266,6 +469,8 @@ ends up empty. When that happens, you reinitialize it to its default value::
# Prints "apple", "orange", "pear", or "banana" every time the code runs.
return random_fruit

.. code-tab:: csharp

When running the above code, there is a chance to get the same fruit twice in a
row. Once we picked a fruit, it will no longer be a possible return value unless
the array is now empty. When the array is empty, we reset it back to its default
Expand All @@ -281,7 +486,10 @@ time, or anything else.
To achieve this, you can use random *noise* functions. Noise functions are
especially popular in procedural generation to generate realistic-looking
terrain. Godot provides :ref:`class_opensimplexnoise` for this, which supports
1D, 2D, 3D, and 4D noise. Here's an example with 1D noise::
1D, 2D, 3D, and 4D noise. Here's an example with 1D noise:

.. tabs::
.. code-tab:: gdscript GDScript

var noise = OpenSimplexNoise.new()

Expand All @@ -297,3 +505,21 @@ terrain. Godot provides :ref:`class_opensimplexnoise` for this, which supports
# Prints a slowly-changing series of floating-point numbers
# between -1.0 and 1.0.
print(noise.get_noise_1d(i))

.. code-tab:: csharp

public override void _Ready()
{
var noise = new OpenSimplexNoise();
GD.Randomize();
// Configure the OpenSimplexNoise instance.
noise.Seed = (int)GD.Randi();
noise.Octaves = 4;
noise.Period = 20.0f;
noise.Persistence = 0.8f;

for (int i = 0; i < 100; i++)
{
GD.Print(noise.GetNoise1d(i));
}
}