astro-pi / beta-testing

Bug tracker for the Trinket Sense HAT emulator
https://trinket.io/sense-hat
3 stars 0 forks source link

Need to update to new SenseStick API #19

Closed waveform80 closed 8 years ago

waveform80 commented 8 years ago

The bug_game.py demo uses methods from SenseStick that were removed (or made non-public) in the eventual version of SenseStick that got merged for release:

Sorry! I'm just going through all the demos on the actual hat with the merged code to see if anything else needs tweaking. In the meantime, I think bug_game.py probably wants to read like this instead:

#!/usr/bin/python3
import time, math, random, sys
import sense_hat

"""
    A bug game.  Eat the food! Avoid the enemies!

    Modify the starting state in the `state` dict.

    This version handles keypresses with a SenseStick object.
"""

starting_enemies = [ [4,6], [0,4] ]

state = { "bug_x" : 4,
          "bug_y" : 4,
          "bug_rgb" : (250,250,250),
          "food_x" : 2,
          "food_y" : 7,
          "food_rgb" : (0,255,50),
          "level" : 1,
          "enemies" : starting_enemies,
          "enemy_rgb" : (255,50,0) }

start_over_state = dict(state)

sense = sense_hat.SenseHat()
sense.low_light = True

def setscreen():
    """Takes x and y vales and alters screen state. Does not 
    modify state."""
    bug_x = state["bug_x"]
    bug_y = state["bug_y"]
    bug_rgb = state["bug_rgb"]
    food_x = state["food_x"]
    food_y = state["food_y"]
    food_rgb = state["food_rgb"]
    enemies = state["enemies"]
    enemy_rgb = state["enemy_rgb"]

    if sense.low_light:
        zero = 8
    else:
        zero = 48
    brightness = 255 -zero 
    sense.clear((50,100,150))
    sense.set_pixel(food_x, food_y, food_rgb) 
    sense.set_pixel(bug_x, bug_y, bug_rgb)
    for e in enemies:
        sense.set_pixel(e[0], e[1], enemy_rgb)

def distance(x1, y1, x2, y2):
    """returns distance of two points"""
    return math.hypot(x2 - x1, y2 - y1)

def clip(pixels, nmin = 0, nmax = 255):
    """Ensures rgb values are between 0 and 255"""
    return tuple(max(min(nmax, n), nmin) for n in pixels)

def check_pos():
    """Checks for eating food and hitting enemies. Alters state but
    does not redraw screen.  Call setscreen() after this."""
    global state
    bug_x = state["bug_x"]
    bug_y = state["bug_y"]
    food_x = state["food_x"]
    food_y = state["food_y"]
    level = state["level"]
    enemies = state["enemies"]

    weaker = int(10 * (level/2))
    stronger = 10
    radius = 2.5
    fdist = distance(bug_x, bug_y, food_x, food_y)

    for e in enemies:
        edist = distance(bug_x, bug_y, e[0], e[1])
        if edist == 0:
            # Hit an enemy; game over & reset
            sense.show_message("R.I.P. Bug, Level {0}".format(state["level"]))
            state = dict(start_over_state)
            return

    if fdist > radius:
        # Bug is far away; grow weaker
        state["bug_rgb"] = clip([abs(i - weaker) for i in state["bug_rgb"]])
    elif fdist == 0.0:
        # Bug ate food and is healthy again
        state["bug_rgb"] = (255,255,255)
        state["level"] += 1
        state["enemies"] += [[random.randint(0,7), random.randint(0,7)]]
        sense.show_message(str(state["level"]))
        time.sleep(1) 
        # Set food to new location that's not under the bug
        while True:
            state["food_x"] = random.randint(0,7)
            if state["food_x"] != state["bug_x"]:
                break
        while True:
            state["food_y"] = random.randint(0,7)
            if state["food_y"] != state["bug_y"]:
                break
    elif fdist < radius:
        # Bug is close; grow a little stronger
        state["bug_rgb"] = clip([abs(i + stronger) for i in state["bug_rgb"]])

def rand_step(xy):
    """Returns one iteration of a random walk of x,y coordinates"""
    x, y = xy

    new_x = x + random.choice([-1,0,1])
    new_y = y + random.choice([-1,0,1])
    return [ 0 if new_x == 8 else 7 if new_x == -1 else new_x,\
             0 if new_y == 8 else 7 if new_y == -1 else new_y]

def move_enemies():
    global state
    enemies = state["enemies"]
    reserved = [[state["bug_x"], state["bug_y"]],[state["food_x"], state["food_y"]]]
    new_enemies = []
    for e in enemies:
        while True:
            new_e = rand_step(e)
            if new_e not in reserved:
                break
        new_enemies.append(new_e)
    state["enemies"] = new_enemies
    setscreen()

def draw_bug(event):
    """Takes a keypress and redraws the screen"""
    global state
    if event.action == sense_hat.ACTION_RELEASED:
        # Ignore releases
        return
    elif event.direction == sense_hat.DIRECTION_UP:
        state["bug_x"] = state["bug_x"]
        state["bug_y"] = 7 if state["bug_y"] == 0 else state["bug_y"] - 1
    elif event.direction == sense_hat.DIRECTION_DOWN:
        state["bug_x"] = state["bug_x"]
        state["bug_y"] = 0 if state["bug_y"] == 7 else state["bug_y"] + 1
    elif event.direction == sense_hat.DIRECTION_RIGHT:
        state["bug_x"] = 0 if state["bug_x"] == 7 else state["bug_x"] + 1
        state["bug_y"] = state["bug_y"]
    elif event.direction == sense_hat.DIRECTION_LEFT:
        state["bug_x"] = 7 if state["bug_x"] == 0 else state["bug_x"] - 1
        state["bug_y"] = state["bug_y"] 

    # Check to see if anything should happen
    setscreen()
    check_pos()
    setscreen()

# Initial state
setscreen()
sense.set_pixel(state["bug_x"], state["bug_y"], state["bug_rgb"])

last_tick = round(time.time(),1) * 10

while True:
    # Enemies move faster in higher levels
    timer = 20 - (state["level"] % 20)

    # Every so often, move enemies
    tick = round(time.time(),1) * 10
    if (tick % timer == 0) and (tick > last_tick):
        move_enemies()
        last_tick = tick

    # Poll joystick for events. When they happen, redraw screen. 
    for event in sense.stick.get_events():
        draw_bug(event)
waveform80 commented 8 years ago

And here's an updated bug_simple.py (I've just used literals instead of the constants here, just for the sake of demo-ing what they actually are):

#!/usr/bin/python3
import time
from sense_hat import SenseHat
import sys

"""
    A bug on a colored background, responding to keypresses.

    Modify the starting state in the `state` dict.
    The background changes colors based on the bug's xy coords.

    This version handles keypresses with a SenseStick object.
"""

state = { "bug_x" : 4,
          "bug_y" : 4,
          "bug_rgb" : (250,250,250) }

sense = SenseHat()

def setscreen():
    """Takes x and y vales and alters screen state"""
    global state
    x = state["bug_x"]
    y = state["bug_y"]
    if sense.low_light:
        zero = 8
    else:
        zero = 48
    brightness = 255 -zero 
    g = int(((x * 32)/255) * brightness + zero)
    b = int(((y * 32)/255) * brightness + zero)
    r = abs(g - b)
    sense.clear((r,g,b))
    #print(r,g,b)

def draw_bug(event):
    global state
    if event.action == 'released':
        # Ignore releases
        return
    elif event.direction == 'up':
        state["bug_x"] = state["bug_x"]
        state["bug_y"] = 7 if state["bug_y"] == 0 else state["bug_y"] - 1
        setscreen()
        sense.set_pixel(state["bug_x"], state["bug_y"], state["bug_rgb"])
    elif event.direction == 'down':
        state["bug_x"] = state["bug_x"]
        state["bug_y"] = 0 if state["bug_y"] == 7 else state["bug_y"] + 1
        setscreen()
        sense.set_pixel(state["bug_x"], state["bug_y"], state["bug_rgb"])
    elif event.direction == 'right':
        state["bug_x"] = 0 if state["bug_x"] == 7 else state["bug_x"] + 1
        state["bug_y"] = state["bug_y"]
        setscreen()
        sense.set_pixel(state["bug_x"], state["bug_y"], state["bug_rgb"])
    elif event.direction == 'left':
        state["bug_x"] = 7 if state["bug_x"] == 0 else state["bug_x"] - 1
        state["bug_y"] = state["bug_y"] 
        setscreen()
        sense.set_pixel(state["bug_x"], state["bug_y"], state["bug_rgb"])

# Initial state
setscreen()
sense.set_pixel(state["bug_x"], state["bug_y"], state["bug_rgb"])

try:
    while True:
        for event in sense.stick.get_events():
            draw_bug(event)
except KeyboardInterrupt:
    sys.exit()
waveform80 commented 8 years ago

An updated flappy_click.py:

import sense_hat
from time import sleep, time
from random import randint

sense = sense_hat.SenseHat()
sense.clear()

##Globals
game_over = False
RED = (255,0,0)
BLACK = (0,0,0)
BLUE = (0,0,255)
y = 4
speed = 0
# column index, gap row start, gap size
columns = [ (7, 3, 3)]

def move_columns():
    global columns
    columns = [(c[0] -1, c[1], c[2]) for c in columns]
    columns = [c for c in columns if c[0] >= 0]
    if max([c[0] for c in columns]) == 4:
        gap_size = randint(2,4)
        row_start = randint(1 + gap_size, 6)
        columns.append((7,row_start,gap_size))

def draw_column(col):
    x, gap_start, gap_size = col
    c = [RED] * 8
#    print(col, x, gap_start, gap_size, type(gap_start + gap_size),c)
    c[gap_start - gap_size: gap_start] = [BLACK] * gap_size
    for y, color in enumerate(c):   
        #print(x,y,color, gap_start, gap_size)
        sense.set_pixel(x,y,color)

def draw_screen():
    global columns, y, speed
    sense.clear()
    for c in columns:
        draw_column(c)

def draw_bird():
    global y
    # Draw bird. Negative speed = up
    sense.set_pixel(3,y,BLACK)
    y += speed

    # Stay onscreen
    if y > 7:
        y = 7
    if y < 0:
        y = 0
    sense.set_pixel(3,y,BLUE)
    #print(y)

last_tick = round(time(), 1) * 20

draw_screen()
draw_bird()
redraw_screen = False

while not game_over:
    tick = round(time(), 1) * 20
    if (tick % 20 == 0) and (tick > last_tick):
        move_columns()
        draw_screen()
        last_tick = tick            
        draw_bird()

    events = sense.stick.get_events()
    if events:
        for e in events:
            if e.direction == sense_hat.DIRECTION_UP and e.action == sense_hat.ACTION_PRESSED:
                move_columns()
                draw_screen()
                speed = -1
                draw_bird()
    else:
        speed = +1

        redraw_screen = False

sense.show_message("You Lose", text_colour=(255,0,0))
waveform80 commented 8 years ago

Updated joystick_test.py:

import sense_emu as sense_hat
import time

"""
    Simple utility to test SenseHat joystick and LEDs are working.

    Uses sense_hat.SenseStick for input
"""

screen = sense_hat.SenseHat()

def react(event):
  n = {
      'up':    133,
      'left':  155,
      'right': 166,
      'down':  188,
      }[event.direction]
  color = [(n,n,n)]*64
  screen.set_pixels(color)
  print(event.direction)

print("Test joystick directions. Click joystick to exit")

while True:
  for event in screen.stick.get_events():
    if event.direction == 'middle':
      break
    react(event)
    time.sleep(.001)
davidhoness commented 8 years ago

Hey Dave, yeah they're planning to update the Sense Stick API soon. But you've saved them a job by updating these examples. Many thanks!

eah13 commented 8 years ago

@waveform80 thanks a ton for the updates! We'll get them pushed once the new API is implemented in JS

waveform80 commented 8 years ago

Just FYI - I'm not sure if this is a bug or not: the characters.py script uses some library I'm not familiar with for loading pixels.gif (it's not installed by default on Raspbian, and doesn't appear to be a sense HAT dependency). I have a feeling it might be a really old version of PIL (I vaguely recall "import image" from way back, but my memory my be fooling me).

Either way, I get the impression it's a deliberate variation as the original script (linked to at the bottom of the trinket version) imports PIL and uses the usual Image.open() instead of image(), size instead of getWidth(), etc. so I'm guessing it's something to do with the set of libraries accessible via trinket?

eah13 commented 8 years ago

You're exactly right that it's not a Python module. It's a Skulpt module for handling images that's vaguely inspired by some real Python stuff. I used it as a proof of concept for how to deal with image files, but it's probably not a good example to preserve on the demo since it' won't be backwards compatible

eah13 commented 8 years ago

Changed the title to reflect the overarching issue. We'll deploy the API updates and updated example programs all at once later this week and I'll comment here.

eah13 commented 8 years ago

This is implemented! Check out the updated joystick examples on the demos page, including an much improved flappy_hat.py (now with lives and scoring!) and a visual rock paper scissors game

@waveform80 testing on the new API much appreciated since you're the authority. We're working on an implementation for signal.pause in skulpt but in the meantime you'll need another way to keep the rpogram from terminating. Perhaps in a future version of the library a sense.stick.listen() method that wrapped pause() or etc would be nice to have.

eah13 commented 8 years ago

Also, check out the new sensors.py - it uses the stick API to integrate all three sensor displays into one program.