Skip to content

Instantly share code, notes, and snippets.

@rcruz63
Created March 19, 2013 10:37
Show Gist options
  • Save rcruz63/5195131 to your computer and use it in GitHub Desktop.
Save rcruz63/5195131 to your computer and use it in GitHub Desktop.
Pythonista Scripts
# Card Game
#
# In this game, you have to find matching pairs of cards.
# This scene consists entirely of layers and demonstrates some
# interesting animation techniques.
from scene import *
from random import shuffle
from functools import partial
import sound
class Game (Scene):
def setup(self):
self.root_layer = Layer(self.bounds)
for effect in ['Click_1', 'Click_2', 'Coin_2', 'Coin_5']:
sound.load_effect(effect)
self.deal()
def draw(self):
background(0.0, 0.2, 0.3)
self.root_layer.update(self.dt)
self.root_layer.draw()
def deal(self):
images = ['Rabbit_Face', 'Mouse_Face', 'Cat_Face',
'Dog_Face', 'Octopus', 'Bear_Face',
'Chicken', 'Cow_Face'] * 2
for image in images:
load_image(image)
shuffle(images)
self.root_layer.sublayers = []
self.cards = []
self.selected = []
card_size = 96 if self.size.w > 700 else 64
width = (card_size + 5) * 4
offset = Point((self.size.w - width)/2,
(self.size.h - width)/2)
for i in xrange(len(images)):
x, y = i % 4, i / 4
card = Layer(Rect(offset.x + x * (card_size + 5),
offset.y + y * (card_size + 5),
card_size, card_size))
card.card_image = images[i]
card.background = Color(0.9, 0.9, 0.9)
card.stroke = Color(1, 1, 1)
card.stroke_weight = 4.0
self.add_layer(card)
self.cards.append(card)
self.touch_disabled = False
def touch_began(self, touch):
if self.touch_disabled or len(self.cards) == 0:
return
if len(self.selected) == 2:
self.discard_selection()
return
for card in self.cards:
if card in self.selected or len(self.selected) > 1:
continue
if touch.location in card.frame:
def reveal_card():
card.image = card.card_image
card.animate('scale_x', 1.0, 0.15,
completion=self.check_selection)
self.selected.append(card)
self.touch_disabled = True
card.animate('scale_x', 0.0, 0.15,
completion=reveal_card)
card.scale_y = 1.0
card.animate('scale_y', 0.9, 0.15, autoreverse=True)
sound.play_effect('Click_1')
break
def discard_selection(self):
sound.play_effect('Click_2')
for card in self.selected:
def conceal(card):
card.image = None
card.animate('scale_x', 1.0, 0.15)
card.animate('scale_x', 0.0, 0.15,
completion=partial(conceal, card))
card.scale_y = 1.0
card.animate('scale_y', 0.9, 0.15, autoreverse=True)
self.selected = []
def check_selection(self):
self.touch_disabled = False
if len(self.selected) == 2:
card_img1 = self.selected[0].card_image
card_img2 = self.selected[1].card_image
if card_img1 == card_img2:
sound.play_effect('Coin_5')
for c in self.selected:
c.animate('background', Color(0.5, 1, 0.5))
self.cards.remove(c)
self.selected = []
if len(self.cards) == 0:
self.win()
def new_game(self):
sound.play_effect('Coin_2')
self.deal()
self.root_layer.animate('scale_x', 1.0)
self.root_layer.animate('scale_y', 1.0)
def win(self):
self.delay(0.5, partial(sound.play_effect, 'Powerup_2'))
font_size = 100 if self.size.w > 700 else 50
text_layer = TextLayer('Well Done!', 'Futura', font_size)
text_layer.frame.center(self.bounds.center())
overlay = Layer(self.bounds)
overlay.background = Color(0, 0, 0, 0)
overlay.add_layer(text_layer)
self.add_layer(overlay)
overlay.animate('background', Color(0.0, 0.2, 0.3, 0.7))
text_layer.animate('scale_x', 1.3, 0.3, autoreverse=True)
text_layer.animate('scale_y', 1.3, 0.3, autoreverse=True)
self.touch_disabled = True
self.root_layer.animate('scale_x', 0.0, delay=2.0,
curve=curve_ease_back_in)
self.root_layer.animate('scale_y', 0.0, delay=2.0,
curve=curve_ease_back_in,
completion=self.new_game)
run(Game())
# Cascade Game
#
# This is a complete game that demonstrates drawing images
# and text, handling touch events and combining simple drawing
# with layer animations.
#
# The game is also known as "Same Game".
# It may appear simple at first, but getting a good score
# actually requires some strategy.
from scene import *
from random import randint
from sound import load_effect, play_effect
from functools import partial
# These values are adjusted for the current screen size in
# the setup method, changing them here won't have an effect.
tile_size = 40
cols = 8
rows = 10
class Tile (object):
def __init__(self, image, x, y):
self.offset = Point() # used for falling animation
self.selected = False
self.image = image
self.x, self.y = x, y
def hit_test(self, touch):
frame = Rect(self.x * tile_size + self.offset.x,
self.y * tile_size + self.offset.y,
tile_size, tile_size)
return touch.location in frame
class Game (Scene):
def setup(self):
#Use different sizes on iPad and iPhone:
global tile_size, cols, rows
ipad = self.size.w > 700
tile_size = 64 if ipad else 40
cols = 12 if ipad else 8
rows = 12 if ipad else 10
if not ipad and self.size.h > 480:
rows += 2 #iPhone 5
#Preload some sound effects to reduce latency:
for sound_effect in ['Click_1', 'Error', 'Coin_3']:
load_effect(sound_effect)
self.new_game()
def new_game(self):
#The effects layer is used to display animated text
#overlays for scores and the game over screen:
self.effects = Layer(self.bounds)
images = ['Green_Apple', 'Grapes', 'Tangerine']
self.score = 0
self.game_over = False
self.grid = list()
for i in xrange(cols * rows):
tile = Tile(images[randint(0, len(images)-1)],
i % cols, i / cols)
self.grid.append(tile)
def neighbors(self, tile):
result = []
if tile is None: return result
x, y = tile.x, tile.y
#Check for neighbors with the same image
#in all 4 directions:
directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]
for direction in directions:
neighbor = self.tile_at(x + direction[0],
y + direction[1])
if neighbor is not None and neighbor.image == tile.image:
result.append(neighbor)
return result
def tile_at(self, x, y):
if x < 0 or y < 0 or x >= cols or y >= rows:
return None
return self.grid[y * cols + x]
def touch_began(self, touch):
play_effect('Click_1')
for tile in self.grid:
if tile is not None: tile.selected = False
for tile in self.grid:
if tile is None: continue
if tile.hit_test(touch):
self.select_from(tile, set())
break
def touch_ended(self, touch):
if self.game_over:
#Start a new game if the current game has ended:
self.new_game()
play_effect('Powerup_3')
return
#At least 2 tiles have to be removed:
sel_count = len(filter(lambda(x): x and x.selected,
self.grid))
if sel_count < 2:
play_effect('Error')
for tile in self.grid:
if tile is not None: tile.selected = False
return
#The first tile is 10 points, the second 20, etc.:
score_added = ((sel_count * (sel_count + 1)) / 2) * 10
self.score += score_added
play_effect('Coin_3')
#Show the added score as an animated text layer:
score_layer = TextLayer(str(score_added),
'GillSans-Bold', 40)
score_layer.frame.center(touch.location)
self.effects.add_layer(score_layer)
from_frame = score_layer.frame
to_frame = Rect(from_frame.x, from_frame.y + 200,
from_frame.w, from_frame.h)
score_layer.animate('frame', to_frame, duration=0.75)
score_layer.animate('alpha', 0.0, delay=0.3,
completion=score_layer.remove_layer)
#Remove selected tiles:
for i in xrange(len(self.grid)):
tile = self.grid[i]
if tile is None: continue
if tile.selected:
self.grid[i] = None
#Adjust the positions of the remaining tiles:
self.drop_tiles()
#If at least one tile has a neighbor with the same image,
#another move is possible:
can_move = max(map(len, map(self.neighbors,
self.grid))) > 0
if not can_move:
play_effect('Bleep')
rest = len(filter(lambda x: x is not None, self.grid))
msg = 'Perfect!' if rest == 0 else 'Game Over'
#Show an animated 'Game Over' message:
font_size = 100 if self.size.w > 700 else 50
game_over_layer = TextLayer(msg, 'GillSans', font_size)
game_over_layer.frame.center(self.size.w / 2,
self.size.h / 2)
game_over_layer.alpha = 0.0
self.effects.add_layer(game_over_layer)
#When the animation completes, the game_over flag is set,
#so that the next tap starts a new game:
completion = partial(setattr, self, 'game_over', True)
game_over_layer.animate('alpha', 1.0, duration=1.0,
completion=completion)
game_over_layer.animate('scale_x', 1.2, autoreverse=True,
duration=1.0)
game_over_layer.animate('scale_y', 1.2, autoreverse=True,
duration=1.0)
def drop_tiles(self):
new_grid = [None for x in xrange(len(self.grid))]
shift = 0
for col in xrange(cols):
drop = 0
col_empty = True
for row in xrange(rows):
tile = self.tile_at(col, row)
if tile is None:
drop += 1
else:
col_empty = False
new_y = tile.y - drop
new_x = tile.x - shift
tile.offset.y += tile_size * (tile.y - new_y)
tile.offset.x += (tile.x - new_x) * tile_size
tile.x, tile.y = new_x, new_y
new_grid[new_y * cols + new_x] = tile
if col_empty:
shift += 1
self.grid = new_grid
def select_from(self, tile, visited, count=1):
#Recursively select all neighboring tiles
#with the same image:
tile.selected = True
visited.add(tile)
n = self.neighbors(tile)
for neighbor in n:
if neighbor in visited: continue
if neighbor.image == tile.image:
count = self.select_from(neighbor, visited, count) + 1
return count
def draw(self):
background(0, 0.1, 0.2)
collision = False
falling = False
#Adjust the falling animation speed based
#on the current framerate:
fall_speed = self.dt * 700
#Draw all the tiles:
draw_selected = False
tint(1.0, 1.0, 1.0)
for tile in self.grid:
if tile is None: continue
if draw_selected != tile.selected:
tint(1.0, 1.0, 1.0, 0.5 if tile.selected else 1.0)
draw_selected = tile.selected
image(tile.image,
tile.x * tile_size + tile.offset.x,
tile.y * tile_size + tile.offset.y,
tile_size, tile_size)
if tile.offset.y > 0:
tile.offset.y = max(0.0, tile.offset.y - fall_speed)
if tile.offset.y == 0.0: collision = True
falling = True
#Draw the current score:
tint(1.0, 1.0, 1.0)
w, h = self.size.w, self.size.h
font_size = 60 if self.size.w > 700 else 40
text(str(self.score), 'GillSans', font_size, w * 0.5, h - 50)
#Animate shifting empty columns:
if not falling:
for tile in self.grid:
if tile is None: continue
if tile.offset.x > 0:
tile.offset.x = max(0.0, tile.offset.x - fall_speed)
if tile.offset.x == 0: collision = True
#Play a sound effect if at least one tile has "landed":
if collision:
play_effect('Click_1')
#Update and draw the text effects layer:
self.effects.update(self.dt)
self.effects.draw()
#Always run the game in portrait orientation):
run(Game(), PORTRAIT)
# Clock
#
# An analog clock that demonstrates drawing basic
# shapes with the scene module.
from scene import *
from time import localtime
ipad = False #will be set in the setup method
#Our Clock class inherits from Scene, so that its draw method
#is automatically called 60 times per second when we run it.
class Clock (Scene):
def setup(self):
global ipad
ipad = self.size.w > 700
def should_rotate(self, orientation):
return True
def draw(self):
background(0, 0.2, 0.3)
t = localtime()
minute = t.tm_min
second = t.tm_sec
hour = t.tm_hour % 12
margin = 25 if ipad else 5
r = (min(self.size.w, self.size.h) / 2) - margin * 2
center = Point(self.size.w/2, self.size.h/2)
#Draw the clock face:
fill(0.8, 0.8, 0.8)
stroke(0.5, 0.5, 0.5)
line_w = 10 if ipad else 5
stroke_weight(line_w)
ellipse(center.x - r, center.y - r, r*2, r*2)
#Draw 12 markers for the hours:
push_matrix()
fill(0.5, 0.5, 0.5)
no_stroke()
translate(center.x, center.y)
digit_w = 20 if ipad else 10
digit_h = 40 if ipad else 20
for i in xrange(12):
rotate(30)
rect(-digit_w/2, r-digit_h, digit_w, digit_h)
pop_matrix()
#Draw the minute hand:
push_matrix()
translate(center.x, center.y)
rotate((-360 / 60) * minute + (-360/60) * (second/60.))
fill(0, 0, 0)
m_height = r - (60 if ipad else 30)
m_width = 20 if ipad else 10
rect(-m_width/2, -m_width/2, m_width, m_height)
pop_matrix()
#Draw the hour hand:
push_matrix()
translate(center.x, center.y)
rotate((-360 / 12) * hour + (-360/12.) * (minute/60.))
fill(0, 0, 0)
h_width = 20 if ipad else 10
h_height = r - (120 if ipad else 60)
rect(-h_width/2, -h_width/2, h_width, h_height)
pop_matrix()
#Draw the second hand:
push_matrix()
translate(center.x, center.y)
rotate((-360 / 60) * second)
fill(1, 0, 0)
s_width = 10 if ipad else 6
s_height = r - (60 if ipad else 20)
rect(-s_width/2, -s_width/2, s_width, s_height)
pop_matrix()
fill(0, 0, 0)
#Draw the small circle in the middle:
r = 20 if ipad else 10
ellipse(center.x - r, center.y - r, r*2, r*2)
#Run the scene that we just defined:
run(Clock())
# Image Effects
# Demonstrates some effects using different modules
# from the Python Imaging Library (PIL).
#
# Tip: You can touch and hold an image in the output
# to copy it to the clipboard or save it to your
# camera roll.
import Image, ImageOps, ImageFilter
from Image import BILINEAR
from math import sqrt, sin, cos, atan2
def sketch(img):
edge_img = img.filter(ImageFilter.CONTOUR)
return ImageOps.grayscale(edge_img)
def color_tiles(img):
size = img.size
small_img = img.resize((size[0]/2, size[1]/2), BILINEAR)
bw_img = small_img.convert('1', dither=False)
gray_img = bw_img.convert('L')
result = Image.new('RGB', size)
tile1 = ImageOps.colorize(gray_img, 'green', 'red')
tile2 = ImageOps.colorize(gray_img, 'purple', 'yellow')
tile3 = ImageOps.colorize(gray_img, 'yellow', 'brown')
tile4 = ImageOps.colorize(gray_img, 'red', 'cyan')
result.paste(tile1, (0, 0))
result.paste(tile2, (size[0]/2, 0))
result.paste(tile3, (0, size[1]/2))
result.paste(tile4, (size[0]/2, size[1]/2))
return result
def twisted(img, strength):
mesh = []
m = 16
w, h = img.size
for x in xrange(w / m):
for y in xrange(h / m):
target_rect = (x * m, y * m, x * m + m, y * m + m)
quad_points = ((x * m, y * m), (x * m, y * m + m),
(x * m + m, y * m + m), (x * m + m, y * m))
quad_list = []
for qx, qy in quad_points:
dx = w/2 - qx
dy = h/2 - qy
d = sqrt(dx**2 + dy**2)
angle = atan2(dx, dy)
angle += max((w/2 - d), 0) * strength
qx = w/2 - sin(angle) * d
qy = h/2 - cos(angle) * d
quad_list.append(qx)
quad_list.append(qy)
mesh.append((target_rect, quad_list))
return img.transform(img.size, Image.MESH, mesh, BILINEAR)
def main():
img = Image.open('Test_Lenna')
img = img.resize((256, 256), Image.BILINEAR)
img.show()
sketch(img).show()
color_tiles(img).show()
twisted(img, 0.02).show()
if __name__ == '__main__':
main()
# Image Warp
#
# Demonstrates an interesting use of the image_quad function
# to distort an image based on touch.
from scene import *
from math import sqrt, sin, pi, floor
import Image
M = 16 # number of vert. and horiz. quads in the mesh (16*16=256)
class ImageWarp (Scene):
def setup(self):
self.offsets = [[(0.0, 0.0) for x in xrange(M+1)] for y in xrange(M+1)]
if self.size.w >= 700:
# Use the original image on iPad:
self.img = 'Test_Lenna'
s = 512
else:
# Resize the image for small screens:
s = 256
img_name = 'Test_Lenna'
img = Image.open(img_name).convert('RGBA')
img = img.resize((s, s), Image.BILINEAR)
self.img = load_pil_image(img)
self.img_size = s
self.m = s / M
def draw_warped_image(self):
m = self.m
for x in xrange(M):
for y in xrange(M):
d = self.offsets
# distances of the 4 corner points from their original position:
d1 = d[x][y]; d2 = d[x + 1][y]
d3 = d[x][y + 1]; d4 = d[x + 1][y + 1]
image_quad(self.img,
# distorted quad:
x * m + d1[0], y * m + d1[1],
x * m + m + d2[0], y * m + d2[1],
x * m + d3[0], y * m + m + d3[1],
x * m + m + d4[0], y * m + m + d4[1],
# source quad in image:
x * m, y * m, x * m + m, y * m,
x * m, y * m + m, x * m + m, y * m + m)
def bulge(self, touch_x, touch_y):
# push mesh vertices away from touch location:
m = self.m
r = self.img_size / 5
for x in xrange(0, M + 1):
for y in xrange(0, M + 1):
offset = self.offsets[x][y]
dist = sqrt((x*m-touch_x + offset[0])**2.0 +
(y*m-touch_y + offset[1])**2.0)
dx = x*m - touch_x
dy = y*m - touch_y
unit_x = dx / dist if dist > 0 else 0.0
unit_y = dy / dist if dist > 0 else 0.0
b = self.dt * 50
if dist < r:
falloff = max(sin(dist/r*pi), 0)
offset_x = unit_x * b * falloff + offset[0]
offset_y = unit_y * b * falloff + offset[1]
self.offsets[x][y] = (offset_x, offset_y)
def revert(self):
for x in xrange(0, M + 1):
for y in xrange(0, M + 1):
offset = self.offsets[x][y]
offset_x = floor(offset[0] - cmp(offset[0], 0))
offset_y = floor(offset[1] - cmp(offset[1], 0))
self.offsets[x][y] = (offset_x, offset_y)
def draw(self):
background(0, 0, 0)
push_matrix()
tx = self.size.w/2 - self.img_size/2
ty = self.size.h/2 - self.img_size/2
translate(tx, ty)
text('Touch the image to deform it.', 'Helvetica-Bold',
18, self.img_size/2, -30, alignment=5)
text('Use two fingers to revert.', 'Helvetica-Bold',
18, self.img_size/2, -60, alignment=5)
self.draw_warped_image()
pop_matrix()
if len(self.touches) ==0:
return
if len(self.touches) > 1:
self.revert()
else:
loc = self.touches.values()[0].location
touch_x, touch_y = loc.x - tx, loc.y - ty
self.bulge(touch_x, touch_y)
run(ImageWarp(), frame_interval=1)
# Markdown Conversion
#
# This script demonstrates how you can convert Markdown documents
# to HTML and view the results in the built-in browser.
import os, tempfile, codecs
import console, clipboard, webbrowser
from markdown2 import markdown
DEMO = '''
# Markdown Conversion Demo
**Markdown** is a plain text formatting language
invented by [John Gruber][1].
You can use this script to convert markdown
documents to html and preview them in the
built-in browser.
Markdown makes it easy to make text **bold** or *italic*,
and to add [links][2].
You can also format block quotes:
> Any intelligent fool can make things bigger, more
> complex, and more violent. It takes a touch of genius
> -- and a lot of courage -- to move in the opposite
> direction.
*-- Albert Einstein*
...and code blocks:
from markdown2 import markdown
text = "*hello world*"
html = markdown(text)
print html
For more detailed information about Markdown,
please read the [introduction and syntax reference][3]
on the project page.
[1]: http://daringfireball.net
[2]: http://omz-software.com/pythonista
[3]: http://daringfireball.net/projects/markdown
'''
# Basic HTML document structure and CSS styling:
TEMPLATE = '''
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style>
body {
font-family: helvetica;
font-size: 18px;
color: #333;
}
blockquote {
border-left: solid 3px #bbb;
margin-left: 0px;
padding-left: 10px;
}
pre {
border: 1px solid #bbb;
border-radius: 3px;
padding: 3px;
background-color: #f8f8f8;
}
code {
font-family: DejaVuSansMono, monospace;
}
#wrapper {
margin: 20px;}
</style>
</head>
<body>
<div id="wrapper">
{{CONTENT}}
</div>
</body>
</html>
'''
def main():
choice = console.alert('Markdown Conversion', '', 'Demo', 'Convert Clipboard')
md = DEMO if choice == 1 else clipboard.get()
html = markdown(md, extras=['smarty-pants'])
tempdir = tempfile.gettempdir()
html_path = os.path.join(tempdir, 'temp.html')
html = TEMPLATE.replace('{{CONTENT}}', html)
with codecs.open(html_path, 'w', 'utf-8') as f:
f.write(html)
file_url = 'file://' + html_path
webbrowser.open(file_url)
if __name__ == '__main__':
main()
# Particles
#
# Create colorful bubbles by moving your fingers.
from scene import *
from random import random
from colorsys import hsv_to_rgb
class Particle (object):
def __init__(self, location):
self.velocity = Size(random() * 4 - 2, random() * 4 - 2)
self.location = location
self.hue = random()
self.alpha = 1.0
class Particles (Scene):
def setup(self):
self.show_instructions = True
self.particles = set()
self.p_size = 64 if self.size.w > 700 else 32
def should_rotate(self, orientation):
return True
def touch_began(self, touch):
if self.show_instructions:
self.show_instructions = False
blend_mode(BLEND_ADD)
def touch_moved(self, touch):
particle = Particle(touch.location)
self.particles.add(particle)
def draw(self):
background(0, 0, 0)
if self.show_instructions:
s = 40 if self.size.w > 700 else 17
text('Move your fingers across the screen.',
'Futura', s, *self.bounds.center().as_tuple())
dead = set()
for particle in self.particles:
r, g, b = hsv_to_rgb(particle.hue, 1, 1)
a = particle.alpha
tint(r * a, g * a, b * a, a)
x, y = particle.location.as_tuple()
s = (2 - a) * self.p_size
image('White_Circle', x - s/2, y - s/2, s, s)
particle.alpha -= 0.02
particle.hue += 0.02
particle.location.x += particle.velocity.w
particle.location.y += particle.velocity.h
if particle.alpha <= 0:
dead.add(particle)
self.particles -= dead
run(Particles())
# Piano
#
# A simple multi-touch piano.
from scene import *
import sound
from itertools import chain
class Key (object):
def __init__(self, frame):
self.frame = frame
self.name = None
self.touch = None
self.color = Color(1, 1, 1)
self.highlight_color = Color(0.9, 0.9, 0.9)
def hit_test(self, touch):
return touch.location in self.frame
class Piano (Scene):
def setup(self):
self.white_keys = []
self.black_keys = []
white_key_names = ['Piano_C3', 'Piano_D3', 'Piano_E3',
'Piano_F3', 'Piano_G3', 'Piano_A3',
'Piano_B3', 'Piano_C4']
black_key_names = ['Piano_C3#', 'Piano_D3#', 'Piano_F3#',
'Piano_G3#', 'Piano_A3#']
for key_name in chain(white_key_names, black_key_names):
sound.load_effect(key_name)
white_positions = range(8)
black_positions = [0.5, 1.5, 3.5, 4.5, 5.5]
key_w = self.size.w
key_h = self.size.h / 8
for i in range(len(white_key_names)):
pos = white_positions[i]
key = Key(Rect(0, pos * key_h, key_w, key_h))
key.name = white_key_names[i]
self.white_keys.append(key)
for i in range(len(black_key_names)):
pos = black_positions[i]
key = Key(Rect(0, pos * key_h + 10, key_w * 0.6, key_h - 20))
key.name = black_key_names[i]
key.color = Color(0, 0, 0)
key.highlight_color = Color(0.2, 0.2, 0.2)
self.black_keys.append(key)
def draw(self):
stroke_weight(1)
stroke(0.5, 0.5, 0.5)
for key in chain(self.white_keys, self.black_keys):
if key.touch is not None:
fill(*key.highlight_color.as_tuple())
else:
fill(*key.color.as_tuple())
rect(*key.frame.as_tuple())
def touch_began(self, touch):
for key in chain(self.black_keys, self.white_keys):
if key.hit_test(touch):
key.touch = touch
sound.play_effect(key.name)
return
def touch_moved(self, touch):
hit_key = None
for key in chain(self.black_keys, self.white_keys):
hit = key.hit_test(touch)
if hit and hit_key is None:
hit_key = key
if key.touch is None:
key.touch = touch
sound.play_effect(key.name)
if key.touch == touch and key is not hit_key:
key.touch = None
def touch_ended(self, touch):
for key in chain(self.black_keys, self.white_keys):
if key.touch == touch:
key.touch = None
run(Piano(), PORTRAIT)
# Function Plotter
import canvas
import console
from math import sin, cos, pi
def draw_grid(min_x, max_x, min_y, max_y):
w, h = canvas.get_size()
scale_x = w / (max_x - min_x)
scale_y = h / (max_y - min_y)
min_x, max_x = round(min_x), round(max_x)
min_y, max_y = round(min_y), round(max_y)
canvas.begin_updates()
canvas.set_line_width(1)
canvas.set_stroke_color(0.7, 0.7, 0.7)
#Draw vertical grid lines:
x = min_x
while x <= max_x:
if x != 0:
draw_x = round(w / 2 + x * scale_x) + 0.5
canvas.draw_line(draw_x, 0, draw_x, h)
x += 0.5
#Draw horizontal grid lines:
y = min_y
while y <= max_y:
if y != 0:
draw_y = round(h/2 + y * scale_y) + 0.5
canvas.draw_line(0, draw_y, w, draw_y)
y += 0.5
#Draw x and y axis:
canvas.set_stroke_color(0, 0, 0)
canvas.draw_line(0, h/2, w, h/2)
canvas.draw_line(w/2, 0, w/2, h)
canvas.end_updates()
def plot_function(func, color, min_x, max_x, min_y, max_y):
#Calculate scale, set line width and color:
w, h = canvas.get_size()
origin_x, origin_y = w * 0.5, h * 0.5
scale_x = w / (max_x - min_x)
scale_y = h / (max_y - min_y)
canvas.set_stroke_color(*color)
canvas.set_line_width(2)
canvas.move_to(origin_x + scale_x * min_x,
origin_y + func(min_x) * scale_y)
#Draw the graph line:
x = min_x
while x <= max_x:
x += 0.05
draw_x = origin_x + scale_x * x
draw_y = origin_y + func(x) * scale_y
canvas.add_line(draw_x, draw_y)
canvas.set_fill_color(*color)
canvas.draw_path()
#Set up the canvas size and clear any text output:
console.clear()
canvas.set_size(688, 688)
#Draw the grid:
area = (-pi, pi, -pi, pi)
draw_grid(*area)
#Draw 4 different graphs (sin(x), cos(x), x^2, x^3):
plot_function(sin, (1, 0, 0), *area)
plot_function(cos, (0, 0, 1), *area)
plot_function(lambda x: x ** 2, (0, 1, 1), *area)
plot_function(lambda x: x ** 3, (1.0, 0.5, 0), *area)
# Lottery Number Generator
from random import choice
import console
#Helper function to input a number within a given range:
def input_number(prompt, min_value, max_value):
value = None
while value is None:
try:
value = int(raw_input(prompt))
except ValueError:
print 'Please enter a number!'
if value < min_value or value > max_value:
print ('Please enter a number between %i and %i!' %
(min_value, max_value))
value = None
return value
#Print the title and input the range of numbers:
console.clear()
title = 'Lottery Number Generator'
print title
print '=' * 40
minimum = input_number('Smallest number: ', 1, 9999)
maximum = input_number('Largest number: ', minimum, 9999)
n = input_number('How many numbers do you want to draw? ',
1, maximum - minimum + 1)
#Pick the numbers and print the results:
all_numbers = range(minimum, maximum + 1)
selection = []
for i in xrange(n):
r = choice(all_numbers)
selection.append(r)
all_numbers.remove(r)
print '=' * 40
print 'Your numbers:', selection
# Snake
#
# Controls: Turn left or right by tapping the
# left or right half of the screen.
from scene import *
from sound import load_effect, play_effect
from random import randint
import pickle
from functools import partial
class Game (Scene):
def setup(self):
w, h = self.size.as_tuple()
self.field = Rect(16, (h - w - 44) / 2 + 16, w - 32, w - 32)
for effect in ['Coin_1', 'Explosion_3', 'Powerup_2']:
load_effect(effect)
self.load_highscore()
self.new_game()
def draw(self):
self.draw_background()
segments = self.get_snake_segments()
self.draw_snake(segments)
if self.paused:
return
self.move_snake(segments)
def touch_began(self, touch):
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
if touch.location.x > self.size.w / 2:
direction_index = (directions.index(self.direction) + 1) % 4
else:
direction_index = directions.index(self.direction) - 1
self.next_direction = directions[direction_index]
def new_game(self):
self.items = set()
self.joints = []
self.joints.append(Point(self.size.w / 2, 256)) #head
self.joints.append(Point(self.size.w / 2, 160)) #tail
self.direction = (0, 1) #up
self.next_direction = None
self.grow = 0
self.speed = 3
self.paused = False
self.score = 0
for i in xrange(3): self.add_item()
def game_over(self):
play_effect('Explosion_3')
self.paused = True
if self.score > self.highscore:
self.highscore = self.score
self.save_highscore()
self.delay(2.0, self.new_game)
def draw_background(self):
#Draw field and scores:
background(0, 0, 0)
tint(1, 1, 1)
text(str(self.score), 'Futura', 40, self.size.w/2, self.size.h - 50)
if self.score < self.highscore:
tint(0.6, 0.6, 0.6)
else:
tint(0, 1, 0)
text('Highscore: ' + str(self.highscore),
'Futura', 24, self.size.w/2, 50)
fill(0, 0.45, 0.65)
rect(*self.field.as_tuple())
#Draw collectable items:
fill(1, 0, 0)
for item in self.items:
ellipse(item.x - 10, item.y - 10, 20, 20)
def get_snake_segments(self):
#Calculate the rectangles that connect the joints:
segments = []
prev_joint = None
for joint in self.joints:
if prev_joint is not None:
p1 = prev_joint
p2 = joint
if p1.x == p2.x:
segments.append(Rect(p1.x - 15, p1.y, 30, p2.y - p1.y))
else:
segments.append(Rect(p1.x, p1.y - 15, p2.x - p1.x, 30))
prev_joint = joint
return segments
def draw_snake(self, segments):
fill(1, 0.8, 0.25)
stroke(0, 0, 0)
stroke_weight(1)
for joint in self.joints:
ellipse(joint.x - 15, joint.y - 15, 30, 30)
for segment in segments:
rect(*segment.as_tuple())
stroke_weight(0)
for joint in self.joints:
ellipse(joint.x - 14, joint.y - 14, 28, 28)
#Draw eyes:
fill(0, 0, 0)
stroke(1, 1, 1)
stroke_weight(2)
head = self.joints[0]
if abs(self.direction[0]) > 0:
ellipse(head.x - 10 * self.direction[0] - 4, head.y + 2, 8, 8)
ellipse(head.x - 10 * self.direction[0] - 4, head.y - 10, 8, 8)
else:
ellipse(head.x + 2, head.y - 10 * self.direction[1] - 4, 8, 8)
ellipse(head.x - 10, head.y - 10 * self.direction[1] - 4, 8, 8)
stroke_weight(0)
def move_snake(self, segments):
hx = self.joints[0].x
hy = self.joints[0].y
tx = self.joints[-1].x
ty = self.joints[-1].y
collected = set()
for i in xrange(self.speed):
#Move head:
hx += self.direction[0]
hy += self.direction[1]
if self.next_direction is not None:
if hx % 32 == 0 and hy % 32 == 0:
self.joints.insert(1, Point(hx, hy))
self.direction = self.next_direction
self.next_direction = None
#Don't move the tail while growing:
if self.grow > 0:
self.grow -= 1
else:
#Move the tail towards the last joint:
tx += cmp(self.joints[-2].x, tx)
ty += cmp(self.joints[-2].y, ty)
#When the joint is reached, remove it:
if tx == self.joints[-2].x and ty == self.joints[-2].y:
del self.joints[-1]
#Update head and tail positions:
self.joints[0].x = hx
self.joints[0].y = hy
self.joints[-1].x = tx
self.joints[-1].y = ty
#Check collisions:
head_rect = Rect(hx - 15, hy - 15, 30, 30)
for segment in segments[2:]:
if head_rect.intersects(segment):
self.game_over()
if hx < self.field.left() + 16 or hx > self.field.right() - 16:
self.game_over()
elif hy < self.field.bottom() + 16 or hy > self.field.top() - 16:
self.game_over()
#Collect items:
for item in self.items:
if hx == item.x and hy == item.y:
collected.add(item)
self.collect_items(collected)
def collect_items(self, collected):
if len(collected) > 0: play_effect('Coin_1')
for item in collected:
self.items -= collected
self.grow += 32
self.score += 10
self.add_item()
#Speed up for every 10 collected items:
if len(collected) > 0 and self.score % 100 == 0:
self.speed = min(8, self.speed + 1)
play_effect('Powerup_2')
#Pause the game for 1 second when the speed is increased:
self.paused = True
self.delay(1.0, partial(setattr, self, 'paused', False))
def add_item(self):
x = randint(0, int(self.field.w / 32) - 1) * 32 + 32
y = randint(0, int(self.field.h / 32) - 1) * 32 + self.field.y + 16
self.items.add(Point(x, y))
def load_highscore(self):
try:
with open('snake_highscore', 'r') as f:
self.highscore = pickle.load(f)
except IOError:
self.highscore = 0
def save_highscore(self):
with open('snake_highscore', 'w+') as f:
pickle.dump(self.highscore, f)
run(Game(), PORTRAIT)
# Stopwatch
#
# A simple stopwatch that demonstrates the scene module's text
# drawing capabilities.
from scene import *
from time import time
from math import modf
from itertools import chain
import string
class Stopwatch (Scene):
def setup(self):
self.start_time = 0.0
self.stop_time = 0.0
self.running = False
#Render all the digits as individual images:
self.numbers = {}
font_size = 150 if self.size.w > 700 else 60
for s in chain(string.digits, [':', '.']):
#render_text returns a tuple of
#an image name and its size.
self.numbers[s] = render_text(s, 'Helvetica-Bold', font_size)
def should_rotate(self, orientation):
return True
def draw(self):
background(0, 0, 0)
#Format the elapsed time (dt):
dt = 0.0
if self.running:
dt = time() - self.start_time
else:
dt = self.stop_time - self.start_time
minutes = dt / 60
seconds = dt % 60
centiseconds = modf(dt)[0] * 100
s = '%02d:%02d.%02d' % (minutes, seconds, centiseconds)
#Determine overall size for centering:
w, h = 0.0, self.numbers['0'][1].h
for c in s:
size = self.numbers[c][1]
w += size.w
#Draw the digits:
x = int(self.size.w * 0.5 - w * 0.5)
y = int(self.size.h * 0.5 - h * 0.5)
for c in s:
img, size = self.numbers[c]
image(img, x, y, size.w, size.h)
x += size.w
def touch_began(self, touch):
if not self.running:
if self.start_time > 0:
#Reset:
self.start_time = 0.0
self.stop_time = 0.0
else:
#Start:
self.start_time = time()
self.running = True
else:
#Stop:
self.stop_time = time()
self.running = False
run(Stopwatch())
# Zen
#
# Prints the Zen of Python with a color gradient.
# This demonstrates the use of the console module to set
# the output's font and color.
import console
import sys
import codecs
from colorsys import hsv_to_rgb
from StringIO import StringIO
#Suppress the output while importing 'this':
prev_out = sys.stdout
sys.stdout = StringIO()
import this
sys.stdout = prev_out
console.clear()
#Decode the 'Zen of Python' text and split to a list of lines:
decoder = codecs.getdecoder('rot_13')
text = decoder(this.s)[0]
lines = text.split('\n')
#Print the title (first line):
console.set_font('Futura', 22)
console.set_color(0.2, 0.2, 0.2)
print lines[0]
#Print the other lines in varying colors:
hue = 0.45
for line in lines[1:]:
r, g, b = hsv_to_rgb(hue, 1.0, 0.8)
console.set_color(r, g, b)
console.set_font('Futura', 16)
print line
hue += 0.02
#Reset output to default font and color:
console.set_font()
console.set_color()
@jglee72
Copy link

jglee72 commented Mar 30, 2017

Thanks. Amazing set of scripts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment