A gallery of fractals I made on the software Fractal Zoomer




















12/9/21





A gallery of fractals I made on the software Fractal Zoomer
12/9/21
Pig is a simple game of luck and gamble, where two players take turns to roll a dice. On a player’s go, they add the number on the dice to their score for that go. If they land on 1, their score for that go is set to 0 and it becomes the other player’s turn. Before each roll, the player can decide if they want to “stick” or roll again. If they choose to stick, their score for that go gets added to their overall score. The aim of the game is to have a higher score than your opponent at the end of n rounds.
I broke this game down into 3 parts.
p1 = 0
p2 = 0
rounds = 10
playing = True
draw = False
winner = "NONE"
# Mainloop
for r in range(1, rounds + 1):
print("-----Round %d-----" % r)
print("Player 1 score: %i" % p1)
print("Player 2 score: %i" % p2)
p1 += move("p1")
p2 += move("p2")
if p1 > p2:
winner = "p1"
elif p2 > p1:
winner = "p2"
else:
draw = True
if draw:
print("-----DRAW-----")
print("Player 1 score: %i" % p1)
print("Player 2 score: %i" % p2)
else:
print(f"----{winner} WINS-----")
print("Player 1 score: %i" % p1)
print("Player 2 score: %i" % p2)
I broke this game down into 3 parts.
Part 1
Part one is the structure of the game. This includes the main loop, adding to the players’ overall scores and printing the winner at the end.
def move(player):
rolling = True
die = 0
round_score = 0
rolls = 0
# User
if player == "p1":
print("\n---YOUR GO---\n")
while rolling:
op = input("Do you want to roll (r) or stick (s)?")
if op.lower().strip() == "r":
die = random.randint(1, 6) # Creating the random number
round_score += die
print("\nThe dice landed on %i!" % die)
rolls += 1
if die == 1 and rolls != 1:
print("0 points for this round!\n")
return 0
print("Total for this round: %d" % round_score, "\n")
else:
print("You got %d points for this round!" % round_score, "\n")
return round_score
Part 2
Part 2 is where the player inputs their decisions into the game. You can choose to roll (r) or stick (s).
# Computer
print("\n---COMPUTER'S GO---\n")
while rolling:
time.sleep(1)
die = random.randint(1, 6)
round_score += die
rolls += 1
print("The computer landed on %i" % die)
if die == 1 and rolls != 1: # Cannot get out on the first go
print("0 points for this round!\n")
return 0
print("The computer's total is %i" % round_score, "\n")
v = (round_score / rolls) / 6 # Getting the value
prob = (rolls ** (v * 4)) / 50 # Getting the probability
if random.random() < prob:
print("The computer has decided to stick\n")
return round_score
time.sleep(1)
Part 3
Part 3 is the complicated bit. It is where the computer decides what it wants to do. It uses a formula to calculate a probability on whether it will stick or not.
I mostly used trial and error to make this formula, as I have no idea what numbers change what so I just messed around with it until it looked good. A website that really helped was https://www.desmos.com/calculator. This is a really great website that plots a line representing a formula that you can specify and change. It also has “sliders” so you can quickly change values and see the result.
To make this formula I started breaking down the game to decide on some simple rules you can follow to maximise your score. At the end of the day this game is luck however you can make certain decisions that increase your point gains. Firstly:
For example, if you roll three 6s in a row, that is really lucky so you are not going to want to waste that. Inversely:
This is a general trend however sometimes if you are getting unlucky, you might just want to stick with what you have and not risk the few points you do have.
To resemble this this certain degree of luck, I made a variable called “V”, or “Value”. This variable is a simple percentage (shown as 0 to 1), that shows how lucky your series of rolls are. For example, 6 is absolute maximum value so that will give you a 1, further rolls will change this value for example if you roll a 3 that will bring you down do v = 0.75. This is how I calculated this:
Where:
This gives you the average score, as a number between 1 and 0. This is only one piece of the puzzle however, as this does not take into account the general rule of:
So, I needed to use this value and bring in the number of rolls into account. I started messing around on the quadratic line plotter. I used a slider and the variable “v” to test how the graph changes as the value changes. This is the formula I got:
These numbers make a line that can be used to give the computer a probability of sticking. This is much more accurate and rewarding than linearly changing the probability.
This is how it works:
The number of rolls are shown on the x axis along the bottom and the probability is derived from the y. The computer picks a random number from 0 to 1. If it is below the y value produced by this graph, the computer sticks. So the more space under the line between 1 and 0 show the higher the probability of sticking. Of course, if the line goes above 1, the computer will always stick as there is a 100% chance of it being under the line.
When you move the slider for v, the line responds.
At v = 0.6, the slope’s gradient becomes more gradual. This allows for the computer to take more rolls.
At v = 0.4, the gradient is very shallow so the computer will roll more.
This was the first formula that I looked at and said “that looks alright”, so I wasn’t expecting much. I reckoned it was a bit too subtle and not daring enough to be accurate to human decision. I definitely expected to go through a couple more generations before I got it right. So, as you might imagine, I was very surprised when the computer beat me on my first game. It then went onto beat my friend on it’s second game, both times it looked like we were going to win, but then the computer comes back in the final few rounds. We then went on to beat it three times in a row, proving that it was not completely unbeatable. At home it beat my dad, in the same late game clutch as it had done twice before.
I have got to admit I was very impressed with my program! However, there is a lot that can be improved. This game is based almost solely on luck, so I think I had got the probabilities just good enough so that luck could tip it over the edge for the win. There is still a lot more room for improvement however, as the computer did make some questionable decisions here and there.
There was a situation in one of the games the computer lost. It was the final round, and the computer was about 10 points behind the player. It was the computers go. First roll: 3… And it stuck. Admittedly, there is a pretty low chance of the computer sticking after just rolling 3, but it does flag up a problem in the logic. In order for the computer to win it must have rolled a score which meant it beat the opponent. So, what a human would do is think, “I might as well keep rolling until it means I can beat the opponent as the only other option is a loss”. This is something that I can program in at a later date.
While I was making the formula, I thought of some other methods of helping the computer decide whether to stick or to roll. On of these methods was the “target number” method, where, the computer would roll until it got to a good amount, say 14, and then stick, this would ensure that the computer always tries to get a good sum of points. Also, there should be a way of fining the “sweet spot”. This would be a number with the highest probability to size ratio. Put simply, the biggest number the computer has a good probability of getting.
Taking this method further, you could add in an element of competitiveness. Perhaps, tell the computer that it always has to beat the what the opponent got for their turn. This could backfire however because the player could get a very low number; there’d be no point stooping to that goes’ level when there’s valuable points to be getting earnt to put the computer ahead of its competition.
Hiding behind this seemingly simple game of luck, there is a single, mathematically correct way of playing the game, a way that puts you at the absolute best chances of winning the game. This, is what I intend to find out. There are two ways I see, of finding this optimum method. One, you could do it completely mathematically, using probability and calculations to find the best method. Secondly, the much more fun method, is letting the computer itself decide what the best way of playing the game. I would need to find a way of the computer playing pig with itself over and over again, thousands of times a second, a way of measuring reward – so it knows what went well and what did not go so well, and a way of recording and somehow condensing the data the computer gathers so it can use that data to improve its own strategies.
I expect the computer to come up with some seemingly bizarre “best way” of playing the game, that at first seems completely wrong to humans. For example, “roll 4 every single time” or something strange like that. I think the computer will come up with something like this (that does not look right to humans) because the computer has no bias or misleading instincts that may give humans a disadvantage. The computer understands no aspect of “luck” or getting “unlucky”. All it sees is a probability. And will not be influenced by the feeling of “aww I’m getting so unlucky, lets stick now”. Fundamentally this is what will allow the computer to find a better algorithm than a human, the best algorithm.
This was one of my first major projects when I started programming. I remember starting this project after seeing a small snippet of logic required to search for prime numbers on a Stack Overflow page, and being very surprised by its simplicity.
I began with the most simple search, with the user inputting a single number and getting the code to try every number beneath it using modulo to check if that number fits into it with 0 remainder.
I then expanded my code to have a specific cap; the code would now check every number from 0 to that number to see if they were prime.
To improve the speed of my prime number generator, I implemented a threading system. This used Python’s threading module to split the process of checking every number under the target number into different jobs. I made a script using 4 and 8 threads. So, for example, the 4 threaded version would take the number 80, and give thread 1 the numbers 3 to 20, thread two 21 to 40, thread three 41 to 60, and thread four 61 to 79.
This slowed the code down initially, due to the computational overhead of starting up the threads I presume, but as soon as the cap reached a critical point where the number was large enough, the threaded version of the code overtook the single threaded version.
I went back to this project months later and had another crack at it: here I made a few massive speed improvements.
First of all I set the code to completely skip the even numbers when iterating to the cap. Meaning it never even begins to check if 4, 6, 8… are prime.
Second of all I implemented a similar thing into the iteration for the checker number, meaning the code does not check if 2 or 4 or 10 go into the target number, as all the evens are being skipped now so those numbers are not necessary to check.
Then I made a series of realisations concerning the range of numbers under the current target which are necessary to be checked:
First, I realised that only the numbers under half of the target need to be checked, as numbers above half will just be a repeat of factor pairs with a one of those numbers being below half, essentially the same thing but reversed.
I then found online that this rule goes even further, and only the numbers up to the square of the target number need to be checked. This is because, apart from the square root factor itself, in every other factor pair their must be one number lower than the square root of the number. This is because if you increase one of the numbers in the pair, the other must decrease in order to still reach the same number. This means that if there is a factor for that number, one of those numbers in that pair will be lower than the square root, and checking that number will break the loop, meaning the other number never even has to be reached in order to prove no prime.
A final major breakthrough was made by my friend when he realised the only numbers you have to use to check if a number has factors are primes. This is because in a prime is made up of no factors (other than itself and 1), so you can be sure when using a prime that no number underneath it are factors of it, meaning that the number has essentially already been checked. For instance, the current system checked 3; it divided the target by 3 to see if it fits in perfectly. But by doing this you also inadvertently disprove that any multiple of 3 will ever fit into that number. So 6, 9, 12, 15, 18… can also never be factors, because if they were factors, 3 would also be a factor. So using only primes to check for factors skips out many numbers that have technically already been disproven by a past multiple.
To check if not prime: if any prime under the square root of the number % the number = 0
After implementing theses features – and without any threading – the checker completely blew away the old system, even at the much higher numbers!
Next I need to program this new logic but with threading too!