Pinball (80 lines of Python in Grapheme)

Hi there,
this post is an example on the flexibility provided by the GUI Scripting capabilities of Grapheme and Nexus. In this example we use the GUI Scripting API to write a small (80 lines with comments) Python script that transform a standard XY Chart into a Pinball field.

pinball_01

Background

We use a standard XY chart to display a bouncing ball and a controlled pad. We can draw the ball as a series with a single point and we can do the same for the pad. We then use a simple Python script to enable the ball moving on the screen.

The Python script

Let’s analyze the script in detail.
First, we need to import required Python packages. Note we need to import the GUI Scripting API plus standard Python packages: sys, random and thread.

import sys
import random
import thread
from ichrome import API

We then define two global variables, vPad and running. The first is used to describe the velocity of the Pad along the X direction, while running is used as a global flag to terminate the macro execution either if the ball reach the bottom of the game field or if the user press e.

# Common global variables
vPad = 0.0
running = True

Now we need to enable users to control the Pad. We do that by defining a function that listens to keyboard inputs, via API.pause() and modify the Pad velocity accordingly. We also add a check so that when users press e the variable running is set to false and macro execution is terminated.

# PAD controller loop 
def controller():
    global running
    global vPad

    while running:
        ch = API.pause()
        if ch=='e':        # 'e' pressed => EXIT GAME
            running = False
        elif ch=='a':
            vPad = -1.25   # 'a' pressed => move RIGHT
        elif ch =='s':
            vPad =  1.15   # 's' pressed => move LEFT
    return

Within the main body of the macro, we set the initial velocity and position of the ball. Note that the ball starts to fall down from chart coordinates y=1 and has an initial vertical velocity of -1. To assure each game differs from the previous one, we set random components for horizontal position and velocity.

# initial settings, PAD in the middle, random direction
xPad = 0.5
v = [ random.random(), -1.0 ]
x = [ random.random(),  1.0 ] 
dt = 0.015

Now we set things up to use table field as support table to feed the XY chart. We make sure that the receiving table has 4 columns and we keep track of them to make easier to set their value later on within the script. Finally we create a new row.

# Create support table, view and columns
table = API.createTable("field",True)
cols = [
   table.createColumn("X",   "double"),
   table.createColumn("Y",   "double"),
   table.createColumn("XPAD","double"),
   table.createColumn("ypad","double") ]
view = table.getView()
row = table.createRow()

We start the controller thread, so that user is immediately able to control the pad:

# use a separate thread for PAD controller
thread.start_new_thread( controller, () )

Finally we start the main drawing loop. The loop simply assign new positions to the ball and the pad at each iteration, until variable running stay true, i.e. until the user presses e or the ball position reach the bottom of the game field, i.e. when ball y coordinates become lower than 0.0 (see line 39).

Ball and pad positions are updated summing up velocity contributions. User changes pad direction, i.e. horizontal velocity every a or s are pressed (see controlled function above) whilst ball velocity is changed every time a boundary of the game field is hit, see checks at lines 50 and 54.
Please note that in order to make the game slightly more unpredictable, we add a small noise component to the bouncing computation of the ball by a small random perturbation of the velocity after a wall is hit.

# define main moving loop (control BALL and PAD)
i = 0
while running:
    # BALL position (and wall-hit checks)
    for k in xrange(0,2):
        x[k] += v[k]*dt
        
        if x[k]>1:
            x[k] = 1.0
            v[k] *= -1.0 + 0.1 * (0.5-random.random())
        
        if x[k]<0:
            if k==1:
                running = (abs(xPad-x[0])<0.075)
            x[k] = 0.0; 
            v[k] *= -1.0 + 0.1 * (0.5-random.random()) 
    
        row.setValue(cols[k], x[k]); 

    # game PAD position 
    xPad += vPad * dt 
    if xPad>1:
        xPad = 1
    elif xPad<0:
        xPad = 0

    row.setValue(cols[2], xPad)
    row.setValue(cols[3], -.05)
    API.sleep(0.10)
    
    # increase speed (every 50 steps)
    i = i+1
    if i%50==0:
        dt *= 1.05

print("GAME OVER!!!")

As we want to be sure your work productivity do not fall below a certain level and to do that we need to make sure game difficulty increases over the time, that’s why we progressively increase the speed of the ball at lines 70-71. Please DO NOT remove them!

Downloads

  • the Grapheme project is available for download from here.
  • the latest version of Grapheme can be downloaded from here.