Recreating a Python Simulation in Scratch

Cooriemungle_Water_Tank_Cow_Monster-wikimedia-commonsIn my last post, I added my own modifications to the tutorial  Making Breakout by Leonel at codeNtronix.   He also has a number of tutorials about drawing 3D graphics using python and pygame.  I was intrigued by his simulation of a 3D Starfield made using Python and Pygame.  (Thanks Leonel for creating such useful tutorials).

 

His simulation uses a few basic 3D to 2D algorithms, which I don’t fully understand but they are effective at creating the desired effect.  He created this effect with just 80 lines of source code,  Click here to download the full source code from CodeNTronics.

I analysed the code myself to see if I could recreate this simulation in Scratch.

3D Starfield

Scratch Code:  Takes 31 lines of code and painted backdrop and sprite costume in the paint editor.starfield-scratch-stage-codestarfield-scratch-starsprite-code

 

Python code:  81 lines from CodeNTronics, with comments added by me.

# """
# 3D Starfield Simulation
# Developed by Leonel Machava <leonelmachava@gmail.com>
#
# http://codeNtronix.com
# http://twitter.com/codentronix
# """
import pygame, math
from random import randrange

class Simulation:
    def __init__(self, num_stars, max_depth):  # SM- each star needs an individual variable for x,y,depth.  
        pygame.init()   # SM-- does a bunch of pygame stuff to set up screen and clock.

        self.screen = pygame.display.set_mode((640, 480))
        pygame.display.set_caption("3D Starfield Simulation (visit codeNtronix.com)")

        self.clock = pygame.time.Clock()
        self.num_stars = num_stars
        self.max_depth = max_depth

        self.init_stars()  # SM- Tells the program to make the list of stars

    def init_stars(self):
        """ Create the starfield """
        self.stars = []
        for i in range(self.num_stars):
            # A star is represented as a list with this format: [X,Y,Z]
            star = [randrange(-25,25), randrange(-25,25), randrange(1, self.max_depth)]
            self.stars.append(star)

    def move_and_draw_stars(self):
       # """ Move and draw the stars   """   SM Converts the xy coordinates to cartesian to centre stars on screen
        origin_x = self.screen.get_width() / 2
        origin_y = self.screen.get_height() / 2

        for star in self.stars:
            # The Z component is decreased on each frame. SM-- Star Moves closer
            star[2] -= 0.19

            # If the star has past the screen (I mean Z<=0) then we
            # reposition it far away from the screen (Z=max_depth)
            # with random X and Y coordinates.
            if star[2] <= 0:
                star[0] = randrange(-25,25)
                star[1] = randrange(-25,25)
                star[2] = self.max_depth

            # Convert the 3D coordinates to 2D using perspective projection.
            k = 128.0 / star[2]   # SM I don't know where Leonel got this constant 128/depth but it works
            x = int(star[0] * k + origin_x)  #SM-- moves starfield origin towards centre of screen
            y = int(star[1] * k + origin_y)

            # Draw the star (if it is visible in the screen).
            # We calculate the size such that distant stars are smaller than
            # closer stars. Similarly, we make sure that distant stars are
            # darker than closer stars. This is done using Linear Interpolation.
            if 0 <= x < self.screen.get_width() and 0 <= y < self.screen.get_height():
                size = (1 - float(star[2]) / self.max_depth) * 5
                shade = (1 - float(star[2]) / self.max_depth) * 255
                self.screen.fill((shade,shade,shade),(x,y,size,size))

    def run(self):
        """ Main Loop """
        while 1:
            # Lock the framerate at 50 FPS.
            self.clock.tick(50)

            # Handle events.
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    return

            self.screen.fill((0,0,0))
            self.move_and_draw_stars()
            pygame.display.flip()

if __name__ == "__main__":
    Simulation(256, 32).run()

Reflection:

The python program presents a much more realistic starfield and is much more versatile. But the scratch program accomplishes the same task in 1/4 the length and it is much easier for me to tinker with the variables and formulas to change the appearance in SCRATCH. It is an accomplishment that I can understand enough of Python to recreate the essential features of a program in SCRATCH. To use the analogy of spoken languages, I think that I have progressed to the point that I can understand a bit of what I read and hear in PYTHONESE, but I cannot yet speak it like a native. This is very good progress for 10 days. To extend the spoken language analogy, one of the common exercises for people learning a second language is to translate passages from one language to another.  That is exactly what I attempted here, and I am quite satisfied with the results.

One thought on “Recreating a Python Simulation in Scratch

Leave a Reply