Last Update: 19.12.2007. By kerim in natural | pygame | python | tutorial | Algorithms
A long time ago i spend a lot of my resources programming fractals and “natural” phenomenons. Since my last adventure with pygame i was playing around with the idea of implementing some algorithms of old times in python anew and use pygame to test them. A small module that creates screens with stars on it is what i will speak about here.
I implemented three small but usefull algorithms:
Although i used pygame for the implementation and although you need it if you want to test my code, it is independend of any (external) library you might use for your game, app, rendering. All you have to do is to overwrite the “draw” method in your own class.
So here is the way to do it:
Pygame Framework to test and Starfields-skeleton
Use the following code below for testing. Just take the comments away from the function call you want to use. As you can see i have put my class in a module called starfields located in a package called natural.
#! /usr/bin/env python
import pygame, sys
from natural.starfields import Starfields
from pygame.color import THECOLORS
from math import pi
def main():
width=800
height=600
displaymode=(width,height)
screen = pygame.display.set_mode(displaymode)
pygame.display.set_caption('Starfield')
#an array of colors for the stars. probability is determined by the amount of times a color is mentioned
colors=[THECOLORS["white"],THECOLORS["yellow"], THECOLORS["white"],THECOLORS["red"],THECOLORS["white"]]
s=Starfields(screen,width,height)
#s.createRandomStars(100, colors)
#s.createStarsByProbability(10, colors)
turn = 45.0 * 2 * pi / 360.0 #calculate in rad
deg = 270.0 * 2 * pi / 360.0
#s.createElipticStarfield(1000, colors, (300,200), (200,100), turn)
s.createSpiralGalaxy(colors,(300,200), (100,50),turn,deg)
while True:
pygame.display.update()
event = pygame.event.poll()
if event.type == pygame.QUIT:
sys.exit()
if __name__=="__main__":
main()
Code for the Starfields - class (Skeleton):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import random
from math import sin, cos,pi
try:
import pygame
except:
print "pygame not available, drawing won't work"
class Starfields():
def __init__(self,screen, width, height):
self.screen=screen
self.width=width
self.height=height
random.seed()
"""
overwrite this for your own graphics library
"""
def draw(self,x,y,color):
pygame.draw.circle(self.screen,color,(x,y),0)
Initialization needs some “screen” object and the width and height of it. pygame is NOT needed for you LATER. I only included it because i wanted to test the module. You can later adapt rather easily by supplying whatever “surface” you want to draw on and adapting the draw method. For example you might provice a screen of None and in the draw method only assemble an array of data etc.
Doing the starfield
First i used a somewhat simple way to create a starfield. I iterated over the whole screen (pixel by pixel) and rolled some dice checking if there would be a star at that pixel. If so then i would determine which color it had and draw it. The way works, but it costs you a huge amount of time.
So instead of doing that you should rather implement a simple algorithm that takes the amount of stars to set and then just determines where to put them and how they should look like.
def createRandomStars(self,amount,colors):
lencol=len(colors)
for star in range(0,amount):
self.draw(random.randint(0,self.width),random.randint(0,self.height),colors[random.randint(0,lencol-1)])
2: Doing the cloud
A cloud is normally deternmined by an elipse with a radius in x direction and one in y, as well as a center. The algorithm is rather simple again. For each star we pick one point at the outer border of the elipse. We first determine the degree randomly. If now we would calculate the positions for x and y position by multilying the radiuses (rx and ry) with the cosinus or sinus then we would create a dotted elipse with the stars all on the edge of it. So we need to move the stars randomly toward the center, with the majority of stars near it and only few at the edge of the elipse. This is done when calculating the insideFactor. It is allways below or equal to 1 and multiplying it with itself will further decrease the value. Around 50 % of all stars should be in the first 25% of the distance between center and outer rim. Finally i also included a variable “turn” which will turn the cloud by a given angle, so that you do not always have the standard “left to right” cloud.
def createElipticStarfield(self, amount, colors, center, radius, turn=0):
lencol=len(colors)
rx,ry=radius
x,y=center
for star in range(0,amount):
degree=random.randint(0,360)
degree= 2.0 * degree * pi / 360.0
#(sin(degree)*rx/ypos=cos(degree)*ry) would form the elipse
#since we need to draw inside with the density increaing near the center we must include
#some factor (0..1)
insideFactor=float(random.randint(0,10000))/10000
insideFactor=insideFactor*insideFactor
xpos=sin(degree)*round(insideFactor*rx)
ypos=cos(degree)*round(insideFactor*ry)
if turn!=0:
xptemp=cos(turn)*xpos+sin(turn)*ypos
yptemp=-sin(turn)*xpos+cos(turn)*ypos
xpos=xptemp
ypos=yptemp
self.draw(x+xpos,y+ypos,colors[random.randint(0,lencol-1)])
3: Doing the spiral galaxy
Now THIS was rather complicated. The first step is to create ONE single cloud in the center of the spiral galaxy. Then we must create the arms. thats done by putting smaller clouds on the path of those arms. In each iteration we swap between the two spiral arms when putting the clouds. The factors for sizes are actually just the result of several hours of trying.
def createSpiralGalaxy(self, colors, center, size, turn=0, deg=0, dynsizefactor=50, sPCFactor=8):
sx,sy=size
sx=2.0*sx*pi/360.0
sy=2.0*sy*pi/360.0
x,y=center
swap=True
xp1=round(deg/pi*sx/1.7)*dynsizefactor
yp1=round(deg/pi*sy/1.7)*dynsizefactor #factors for dynamic sizing
print xp1,yp1
self.createElipticStarfield(5*(xp1+yp1),colors,center,(xp1,yp1),turn)
#this was the central cloud
#now for the smaller ones in the spiral arms
mulStarAmount=(xp1+yp1)/sPCFactor #factor for amount of stars per cloud
n=0.0
while n<=deg:
swap = not swap
xpos=(cos(n)*((n*sx))*dynsizefactor)
ypos=(sin(n)*((n*sy))*dynsizefactor)
xp1=cos(turn)*xpos + sin(turn)*ypos
yp1=-sin(turn)*xpos + cos(turn)*ypos
sizetemp=2+(mulStarAmount*n)
if swap:
self.createElipticStarfield(int(sizetemp/2),colors,(x+xp1,y+yp1),(sizetemp,sizetemp), turn)
else:
self.createElipticStarfield(int(sizetemp/2),colors,(x-xp1,y-yp1),(sizetemp,sizetemp), turn)
angle=random.randint(0,4)+1
n+= 2.0* angle *pi / 360.0
Finally:
I would like to have some feedback on this. If you have a good picture send it to me. If you have an idea on how to improove the code or add some functionality to beautify the result … send it to me.