Easterhegg session

This commit is contained in:
Marcel Hellkamp 2014-04-19 21:00:05 +02:00
parent 89e9d7c441
commit 5a4cb85bea
3 changed files with 226 additions and 127 deletions

108
brain.py Normal file
View file

@ -0,0 +1,108 @@
import pixelflut
import os
import time
def guess_IP():
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(("google.com", 80))
return s.getsockname()[0]
finally:
s.close()
IP = guess_IP()
port = 1234
text = 'P1XELFLUT! v%s (%d)\n' % (
pixelflut.__version__,
os.stat(__file__).st_mtime)
text += 'Connect to %s:%d\n\n' % (IP, port)
text += '>>> HELP\n'
text += '>>> SIZE\n'
text += '>>> TEXT x y text\n'
text += '>>> PX x y [RRGGBB (hex)]\n'
text += '... and more ...\n\n'
text += 'H A C K O N\n'
@on('LOAD')
def callback(c):
c.load_font('./font.png')
c.set_title('@ %s:%d' % (IP, port))
@on('UNLOAD')
def callback(canvas):
canvas.text(5, 5, 'Reloading ...')
@on('RESIZE')
def on_resize(c):
c.text(5, 5, 'Screen Size: %dx%d' % c.get_size())
@on('CONNECT')
def on_connect(c, client):
pass
#print c.clients.keys()
@on('KEYDOWN-c')
def on_key_c(c):
c.clear()
pixelflut.async(c.text, 5, 5, text, delay=0.1)
@on('KEYDOWN-s')
def on_key_s(c):
import os
i, mask = 0, 'screen%05d.png'
while os.path.exists(mask%i): i += 1
c.save_as(mask%i)
c.text(5,5, 'Saved as %s' % mask % i)
@on('COMMAND-HELP')
def on_help(canvas, client):
client.send(text)
@on('COMMAND-TEXT')
def on_text(canvas, client, x, y, *words):
x, y = int(x), int(y)
canvas.text(x, y, ' '.join(words), delay=0.1)
@on('COMMAND-SIZE')
def on_size(canvas, client):
client.send('SIZE %d %d' % canvas.get_size())
@on('COMMAND-QUIT')
def on_quit(canvas, client):
client.disconnect()
@on('COMMAND-PX')
def on_px(canvas, client, x, y, color=None):
client.last_pixel = time.time()
x, y = int(x), int(y)
if color:
c = int(color, 16)
if c <= 16777215:
r = (c & 0xff0000) >> 16
g = (c & 0x00ff00) >> 8
b = c & 0x0000ff
a = 0xff
else:
r = (c & 0xff000000) >> 24
g = (c & 0x00ff0000) >> 16
b = (c & 0x0000ff00) >> 8
a = c & 0x000000ff
canvas.set_pixel(x, y, r, g, b, a)
else:
r,g,b,a = canvas.get_pixel(x,y)
client.send('PX %d %d %02x%02x%02x%02x' % (x,y,r,g,b,a))
last_save = 0
@on('TICK')
def on_tick(canvas, dt):
global last_save
if time.time() > last_save:
last_save = time.time() + 5
canvas.save_as('save/mov_%d.png' % last_save)
canvas.text(5, 5, text, delay=0)

View file

@ -1,6 +1,6 @@
#coding: utf8 #coding: utf8
__version__ = '0.4' __version__ = '0.5'
import time import time
from gevent import spawn, sleep as gsleep from gevent import spawn, sleep as gsleep
@ -8,75 +8,67 @@ from gevent.server import StreamServer
from gevent.coros import Semaphore from gevent.coros import Semaphore
from gevent.queue import Queue from gevent.queue import Queue
from collections import deque from collections import deque
import pygame
import cairo
import math
import random
import array
import os
import os.path
import logging
log = logging.getLogger('pixelflut')
async = spawn async = spawn
class Client(object): class Client(object):
px_per_tick = 10 px_per_tick = 100
def __init__(self, canvas, socket, address): def __init__(self, canvas):
self.canvas = canvas self.canvas = canvas
self.socket = socket self.socket = None
self.address = address
self.connect_ts = time.time() self.connect_ts = time.time()
# This buffer discards all but the newest 1024 messages # This buffer discards all but the newest 1024 messages
self.sendbuffer = deque([], 1024) self.sendbuffer = deque([], 1024)
# And this is used to limit clients to X messages per tick # And this is used to limit clients to X messages per tick
# We start at 0 (instead of x) to add a reconnect-penalty. # We start at 0 (instead of x) to add a reconnect-penalty.
self.limit = Semaphore(0) self.limit = Semaphore(0)
print 'CONNECT', address
def send(self, line): def send(self, line):
self.sendbuffer.append(line.strip() + '\n') self.sendbuffer.append(line.strip() + '\n')
def disconnect(self): def nospam(self, line):
print 'DISCONNECT', self.address if not self.sendbuffer:
self.socket.close() self.sendbuffer.append(line.strip() + '\n')
del self.canvas.clients[self.address]
def serve(self): def disconnect(self):
if self.socket:
self.socket.close()
self.socket = None
def serve(self, socket):
self.socket = socket
sendall = self.socket.sendall sendall = self.socket.sendall
readline = self.socket.makefile().readline readline = self.socket.makefile().readline
try: try:
while True: while True:
self.limit.acquire()
# Idea: Send first, receive later. If the client is to # Idea: Send first, receive later. If the client is to
# slow to get the send-buffer empty, he cannot send. # slow to get the send-buffer empty, he cannot send.
while self.sendbuffer: while self.sendbuffer:
sendall(self.sendbuffer.popleft()) sendall(self.sendbuffer.popleft())
line = readline() line = readline().strip()
if not line: if not line:
break break
arguments = line.split() arguments = line.split()
command = arguments.pop(0) command = arguments.pop(0)
if command == 'PX': try:
self.on_PX(arguments) self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments)
elif command == 'SIZE': except TypeError, e:
self.on_SIZE(arguments) self.nospam('ERROR %r :(' % e)
else:
self.canvas.fire('COMMAND-%s' % command.upper, self, *arguments)
finally: finally:
self.disconnect() self.disconnect()
def on_SIZE(self, args):
self.send('SIZE %d %d' % self.canvas.get_size())
def on_PX(self, args):
self.limit.acquire()
x,y,color = args
x,y = int(x), int(y)
c = int(color, 16)
if c <= 16777215:
r = (c & 0xff0000) >> 16
g = (c & 0x00ff00) >> 8
b = c & 0x0000ff
a = 0xff
else:
r = (c & 0xff000000) >> 24
g = (c & 0x00ff0000) >> 16
b = (c & 0x0000ff00) >> 8
a = c & 0x000000ff
self.canvas.set_pixel(x, y, r, g, b, a)
def tick(self): def tick(self):
while self.limit.counter <= self.px_per_tick: while self.limit.counter <= self.px_per_tick:
self.limit.release() self.limit.release()
@ -84,11 +76,7 @@ class Client(object):
import pygame
import cairo
import math
import random
import array
class Canvas(object): class Canvas(object):
size = 640,480 size = 640,480
@ -97,47 +85,54 @@ class Canvas(object):
def __init__(self): def __init__(self):
pygame.init() pygame.init()
pygame.mixer.quit() pygame.mixer.quit()
pygame.display.set_caption('P1XELFLUT') self.set_title()
self.screen = pygame.display.set_mode(self.size, self.flags) self.screen = pygame.display.set_mode(self.size, self.flags)
self.ticks = 0 self.ticks = 0
self.width = self.screen.get_width() self.width = self.screen.get_width()
self.height = self.screen.get_height() self.height = self.screen.get_height()
self.clients = {} self.clients = {}
self.events = {} self.events = {}
self.font = pygame.font.Font(None, 17)
def serve(self, host, port): def serve(self, host, port):
self.server = StreamServer((host, port), self.make_client) self.server = StreamServer((host, port), self.make_client)
self.server.start() self.server.start()
return spawn(self._loop) return spawn(self._loop)
def make_client(self, socket, address): def make_client(self, socket, address):
if address in self.clients: ip = address[0]
self.clients[address].disconnect() client = self.clients.get(ip)
self.clients[address] = client = Client(self, socket, address) if client:
self.fire('CONNECT', client) client.disconnect()
client.serve() # This blocks until ready else:
self.fire('DISCONNECT', client) self.clients[ip] = client = Client(self)
try:
self.fire('CONNECT', client)
client.serve(socket) # This blocks until ready
self.fire('DISCONNECT', client)
finally:
client.disconnect()
def _loop(self): def _loop(self):
self.fire('START')
while True: while True:
gsleep(0.01) # Required to allow other tasks to run gsleep(0.01) # Required to allow other tasks to run
if not self.ticks % 10: for e in pygame.event.get():
for e in pygame.event.get(): if e.type == pygame.VIDEORESIZE:
if e.type == pygame.VIDEORESIZE: old = self.screen.copy()
old = self.screen.copy() self.screen = pygame.display.set_mode(e.size, self.flags)
self.screen = pygame.display.set_mode(e.size, self.flags) self.screen.blit(old, (0,0))
self.screen.blit(old, (0,0)) self.width, self.height = e.size
self.width = self.screen.get_width() self.fire('RESIZE')
self.height = self.screen.get_height() elif e.type == pygame.QUIT:
self.fire('RESIZE') self.fire('QUIT')
elif e.type == pygame.QUIT: return
self.fire('QUIT') elif e.type == pygame.KEYDOWN:
return self.fire('KEYDOWN-' + e.unicode)
elif e.type == pygame.KEYDOWN:
self.fire('KEYDOWN-' + e.unicode)
self.ticks += 1 self.ticks += 1
self.fire('TICK', self.ticks) self.fire('TICK', self.ticks)
for c in self.clients.values():
c.tick()
pygame.display.flip() pygame.display.flip()
def on(self, name): def on(self, name):
@ -150,7 +145,10 @@ class Canvas(object):
def fire(self, name, *a, **ka): def fire(self, name, *a, **ka):
''' Fire an event. ''' ''' Fire an event. '''
if name in self.events: if name in self.events:
self.events[name](self, *a, **ka) try:
self.events[name](self, *a, **ka)
except:
log.exception('Error in callback for %r', name)
def get_size(self): def get_size(self):
''' Get the current screen dimension as a (width, height) tuple.''' ''' Get the current screen dimension as a (width, height) tuple.'''
@ -164,28 +162,39 @@ class Canvas(object):
''' Change the colour of a pixel. If an alpha value is given, the new ''' Change the colour of a pixel. If an alpha value is given, the new
colour is mixed with the old colour accordingly. ''' colour is mixed with the old colour accordingly. '''
if a == 0: return if a == 0: return
screen = self.screen
if a == 0xff: if a == 0xff:
self.screen.set_at((x,y), (r,g,b)) screen.set_at((x, y), (r,g,b))
else: elif 0 <= x < self.width and 0 <= y < self.height:
r2,g2,b2,a2 = self.screen.get_at((x, y)) r2, g2, b2, a2 = screen.get_at((x, y))
r = (r2*(0xff-a)+(r*a)) / 0xff r = (r2*(0xff-a)+(r*a)) / 0xff
g = (g2*(0xff-a)+(g*a)) / 0xff g = (g2*(0xff-a)+(g*a)) / 0xff
b = (b2*(0xff-a)+(b*a)) / 0xff b = (b2*(0xff-a)+(b*a)) / 0xff
self.screen.set_at((x, y), (r,g,b)) screen.set_at((x, y), (r,g,b))
def clear(self, r=0, g=0, b=0): def clear(self, r=0, g=0, b=0, a=255):
''' Fill the entire screen with a solid colour (default: black)''' ''' Fill the entire screen with a solid colour (default: black)'''
self.screen.fill((r,g,b)) self.screen.fill((r, g, b))
def save_as(self, filename): def save_as(self, filename):
''' Save screen to disk. ''' ''' Save screen to disk. '''
pygame.image.save(self.screen, filename) pygame.image.save(self.screen, filename)
def load_from(self, filename):
img = pygame.image.load(filename).convert()
self.screen.blit(img, (0,0))
def load_font(self, fname): def load_font(self, fname):
''' Load a font image with 16x16 sprites. ''' ''' Load a font image with 16x16 sprites. '''
self.font_img = pygame.image.load(fname).convert() self.font_img = pygame.image.load(fname).convert()
self.font_res = int(self.font_img.get_width())/16 self.font_res = int(self.font_img.get_width())/16
def set_title(self, text=None):
title = 'P1XELFLUT'
if text:
title += ' ' + text
pygame.display.set_caption(title)
def putc(self, x, y, c): def putc(self, x, y, c):
if not self.font_img: if not self.font_img:
self.load_font('font.png') self.load_font('font.png')
@ -204,3 +213,39 @@ class Canvas(object):
if __name__ == '__main__':
logging.basicConfig()
import optparse
parser = optparse.OptionParser("usage: %prog [options] brain_script")
parser.add_option("-H", "--host", dest="hostname",
default="0.0.0.0", type="string",
help="specify hostname to run on")
parser.add_option("-p", "--port", dest="portnum", default=1234,
type="int", help="port number to run on")
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error("incorrect number of arguments")
canvas = Canvas()
task = canvas.serve(options.hostname, options.portnum)
brainfile = args[0]
mtime = 0
while True:
gsleep(1)
if mtime < os.stat(brainfile).st_mtime:
canvas.fire('UNLOAD')
canvas.events.clear()
try:
execfile(brainfile, {'on':canvas.on, '__file__': brainfile})
except:
log.exception('Brain failed')
continue
canvas.fire('LOAD')
mtime = os.stat(brainfile).st_mtime
task.join()

54
run.py
View file

@ -1,54 +0,0 @@
import pixelflut
def guess_IP():
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(("google.com", 80))
return s.getsockname()[0]
finally:
s.close()
port = 2342
text = 'P1XELFLUT! v%s\n' % pixelflut.__version__
text += 'Connect to %s:%d\n\n' % (guess_IP(), port)
text += '>>> SIZE\n'
text += '>>> PX x y hex-color\n'
text += '... and more ...\n\n'
text += 'H A C K O N\n'
canvas = pixelflut.Canvas()
@canvas.on('START')
def callback(c):
c.load_font('./font.png')
pixelflut.async(c.text, 5, 5, text, delay=0.1)
@canvas.on('RESIZE')
def callback(c):
c.text(5, 5, 'Screen Size: %dx%d' % c.get_size())
@canvas.on('CONNECT')
def callback(c, client):
c.text(5, 5, 'Connect: %s' % client.address[0])
@canvas.on('KEYDOWN-c')
def callback(c):
c.clear()
@canvas.on('KEYDOWN-s')
def callback(c):
import os
i, mask = 0, 'screen%05d.png'
while os.path.exists(mask%i): i += 1
c.save_as(mask%i)
c.text(5,5, 'Saved as %s' % mask % i)
@canvas.on('COMMAND-CLEAR')
def callback(canvas, client, *args):
canvas.clear()
task = canvas.serve('0.0.0.0', port)
task.join()