SLC21 Week6 - Programming Games&Puzzles (part 2)

Assalamualaikum my fellows I hope you will be fine by the grace of Allah. Today I am going to participate in the steemit learning challenge season 21 week 6 by @sergeyk under the umbrella of steemit team. It is about Programming Games&Puzzles. Let us start exploring this week's teaching course.

Learn more about variable types. Subroutines. Practice problems. (1).png

Made with Canva

Role of Random Numbers in the Computer World

Random numbers are fundamental in computing and play critical roles in various domains:

  1. Games: Randomness drives unpredictability and variability in game mechanics, such as generating loot, determining events, or shuffling cards.

  2. Cryptography: Secure communication relies on random numbers to create keys, initialize cryptographic algorithms, and ensure data security.

  3. Simulations: Random numbers are used in Monte Carlo simulations to model complex systems like weather patterns, financial markets, and scientific experiments.

  4. Machine Learning and AI: Random initialization of weights in neural networks and data shuffling during training ensure unbiased and effective learning.

  5. Data Sampling and Testing: Random sampling is used in statistics, research, and testing to ensure representative data and uncover edge cases in software.

  6. Procedural Generation: In fields like video game design, random numbers generate environments, levels, or content procedurally.

  7. Security Features: OTPs (One-Time Passwords) and secure token generation depend on randomness.


Pseudo-random Numbers vs. True Random Numbers

Pseudo-random Numbers:

  • Generated by deterministic algorithms called Random Number Generators (RNGs).
  • Depend on a seed value, meaning the sequence can be reproduced if the same seed is used.
  • Useful for testing and debugging because of their predictability under controlled conditions.

True Random Numbers:

  • Derived from physical phenomena, such as thermal noise, radioactive decay, or atmospheric noise.
  • Unpredictable and cannot be reproduced, making them more suitable for cryptographic applications.

Transforming Pseudo-random Numbers to Be More Random

While pseudo-random numbers inherently follow a pattern due to their algorithmic nature, they can be made more random or "random enough" for practical applications by incorporating external randomness:

  1. Using External Seed Values:

    • Instead of a fixed seed, use dynamic or unpredictable values from the real world:
      • System time: time(0) generates a seed based on the current timestamp.
      • Environmental inputs: System events, mouse movements, or keyboard presses.
  2. Blending with Entropy Sources:

    • Combine multiple sources of randomness, such as system clock values, noise from sensors, or hardware entropy devices.
  3. Cryptographic RNGs (CSPRNGs):

    • Use cryptographic algorithms designed for unpredictability, such as /dev/random and /dev/urandom in Unix systems, or libraries like random.SystemRandom in Python and in C++ <random> library.
  4. Post-processing Techniques:

    • Techniques like hashing or XOR operations can add layers of unpredictability to pseudo-random outputs.
  5. Hardware-based True Random Number Generators (TRNGs):

    • Use specialized hardware to generate randomness based on physical processes. These are common in secure systems, such as hardware security modules (HSMs).

By combining algorithmic and physical randomness, pseudo-random numbers can achieve sufficient unpredictability for most applications. However, for high-stakes scenarios like cryptography, true random numbers are preferred.


image.png

08.12.2024_04.24.42_REC-ezgif.com-video-to-gif-converter.gif

In the above example I have used external seed values with the help of the time(0) to generate more random numbers.



Write a random number generator. Investigate it.

In this random generator I have used LCG method to generate the random numbers instead of using the built in function rand() to generate the random numbers by C++.

  1. Periodicity:
    • In this program the periodicity is the number of random numbers generated before the sequence starts repeating.
    • In the code, a std::unordered_set is used to track previously generated numbers. The program stops and reports if a repetition is found.
  2. Distribution:
  • The output shows the frequency of each number in the sequence.
  • The uniformity of the distribution indicates how "random" the sequence appears.

The periodicity depends on the parameters (m, c, and mod) and the seed. For well chosen parameters the periodicity can approach the modulus (mod). If poorly chosen then the sequence might repeat too soon. The uniformity depends on how well the modulus and multiplier are chosen. For large enough mod the distribution improves but for small mod clustering can occur. LCGs are computationally efficient but are not suitable for cryptographic purposes due to their predictability.

  • I have tried different seed and the number of the random numbers to generate.

  • As I was changing the seed value the behaviour of the random generator was also changing.

  • I used 7 as seed and there occurred repetition in the sequence but as soon as I moved to the larger seed then the repetition decreased or there was no repetition in the sequence at all.

  • Moreover if we use larger value of the mod then we can observe the periodicity and distribution in a better. I used 1000 for the mod and I generated 100 random numbers but you will be surprised to know that there was no any single repetition in the 100 generated numbers. SO I observed if we use larger values for the mod then the repetition is less likely to happen in the generated numbers.

08.12.2024_04.56.33_REC-ezgif.com-video-to-gif-converter.gif

We can perform the same work by using the built in rand() function that is simple, easy and quick way to generate the random numbers.

This random generator uses srand()seeds the random number generator with a unique starting point and time(0) ensures different seeds for each run. rand(): generates a pseudo random number. % 100 limits the range of the generated number. Here it limits it between 0 and 99.



"Deck of Cards" - Find several ways to fill the array 1...N - 1. One way - one point.

Here are five unique methods to fill an array with numbers from 1 to N - 1.

1. Brute Force with Randomness

brute-ezgif.com-video-to-gif-converter.gif

This method uses random numbers to fill the array. If a generated number is already in the array then it skips and generates another. It ensures uniqueness but can be very inefficient for large N as it may repeatedly try numbers already present.


2. Fisher-Yates Shuffle


2-ezgif.com-video-to-gif-converter.gif

The Fisher-Yates algorithm shuffles an array in place efficiently. First, an array of sequential numbers is created (1 to N - 1), then shuffled randomly. It is efficient and widely used for generating random permutations.


3. Using a Set for Unique Numbers


3-ezgif.com-video-to-gif-converter.gif

This method uses a std::unordered_set to ensure each random number is unique. It keeps generating and adding random numbers until the set size reaches N - 1. Finally, the set is converted into an array. It guarantees uniqueness but has additional overhead from using a set.


4. Incremental Filling with Random Swaps

4-ezgif.com-video-to-gif-converter.gif

This method first fills the array sequentially (1 to N - 1), then randomly swaps elements to shuffle the array. It's simple and efficient, though slightly less optimal than Fisher-Yates in theory.


5. Recursive Backtracking

5-ezgif.com-video-to-gif-converter.gif

This method uses backtracking to fill the array recursively. For each position, it tries all numbers (1 to N - 1) and checks if the number is safe to use (i.e., not already used). While conceptually interesting, it is very slow for larger arrays due to its exhaustive nature.


Comparison of Methods

MethodEfficiencyEase of ImplementationScalabilityUse Case
Brute ForceInefficient for large NSimplePoorSmall arrays
Fisher-Yates ShuffleEfficientModerateExcellentRandom permutations
Using a SetModerateSimpleGoodEnsuring uniqueness
Incremental Random SwapEfficientSimpleGoodLightweight shuffling
Recursive BacktrackingVery InefficientComplexPoorTheoretical exploration

Best Method

The Fisher-Yates Shuffle is the most efficient and suitable for practical use. It provides true random permutations with a time complexity of O(N) and is widely used in real-world applications.



Construct a table of the number of operations required to generate a deck of N cards for your methods.

To calculate the number of operations for each method to generate a deck of N cards I will define the operations as the significant steps performed to ensure the array is filled with unique values.

Assumptions and Calculations

  • Brute Force with Randomness:
    • Complexity: O(N^2) for small N because repeated checks for duplicates increase quadratically.
    • Operations include generating a random number and checking for its existence in the array.
  • Fisher-Yates Shuffle:
    • Complexity: O(N) as it shuffles efficiently in place.
    • Operations include swapping elements.
  • Using a Set:
    • Complexity: O(N) for inserting into a set but with additional overhead for hashing.
  • Incremental Filling with Random Swaps:
    • Complexity: O(N), as it swaps elements in a pre-filled array.
  • Recursive Backtracking:
    • Complexity: O(N!), as it exhaustively tries all combinations.

Operations Table

Ncards (N)Brute ForceFisher-Yates ShuffleUsing a SetRandom SwapsRecursive Backtracking
550202520120
672243024720
7982835285,040
812832403240,320
9162364536362,880
102004050403,628,800

How the Numbers are Derived

  1. Brute Force:
    • Estimated as N x N, assuming frequent rejections of duplicate numbers for small N.
    • E.g., for N = 5, 5 x 10 = 50 operations (10 attempts per slot on average).
  2. Fisher-Yates Shuffle:
    • Performs N - 1 swaps, so operations = N x 4 (1 operation for each swap, including random index generation).
  3. Using a Set:
    • For N cards, N insertions into the set and additional operations for hashing and uniqueness checks.
  4. Incremental Filling with Random Swaps:
    • Similar to Fisher-Yates but includes N - 1 swaps directly without a nested loop.
  5. Recursive Backtracking:
    • O(N!): Exponential growth due to the combinatorial nature of the algorithm.

The Fisher-Yates Shuffle is the most suitable method:

  • It consistently requires the least operations and scales linearly with N.
  • The Recursive Backtracking method while interesting, becomes impractical for N > 8.



I invite @wirngo, @wuddi, and @chant to learn gaming and puzzles in programming.


I have compiled all the code of the tasks in a famous online compiler which is OnlineGDB

Sort:  
Loading...

Upvoted! Thank you for supporting witness @jswit.