Added java implementation
This commit is contained in:
parent
5911691434
commit
75b436a29a
8 changed files with 387 additions and 3 deletions
143
pixelflut/brain.py
Normal file
143
pixelflut/brain.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import pixelflut
|
||||
import os
|
||||
import time
|
||||
|
||||
pixelcount = 0
|
||||
|
||||
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 += '$ echo "HELP" | netcat %s %d\n' % (IP, port)
|
||||
text += 'https://github.com/defnull/pixelflut\n'
|
||||
|
||||
help = 'Commands:\n'
|
||||
help += '>>> HELP\n'
|
||||
help += '>>> SIZE\n'
|
||||
help += '>>> QUIT\n'
|
||||
help += '>>> TEXT x y text (currently disabled)\n'
|
||||
help += '>>> PX x y [RRGGBB[AA]]\n'
|
||||
|
||||
@on('LOAD')
|
||||
def callback(c):
|
||||
c.load_font('./font.png')
|
||||
c.set_title('@ %s:%d' % (IP, c.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
|
||||
|
||||
@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('KEYDOWN-k')
|
||||
def on_key_k(c):
|
||||
for ip, client in c.clients.iteritems():
|
||||
client.disconnect()
|
||||
|
||||
@on('COMMAND-HELP')
|
||||
def on_help(canvas, client):
|
||||
client.send(help)
|
||||
|
||||
@on('COMMAND-TEXT')
|
||||
def on_text(canvas, client, x, y, *words):
|
||||
x, y = int(x), int(y)
|
||||
text = ' '.join(words)[:200]
|
||||
canvas.text(x, y, text, delay=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-GODMODE')
|
||||
def on_quit(canvas, client, mode):
|
||||
if mode == 'on':
|
||||
client.pps = 100000
|
||||
else:
|
||||
client.pps = 1000
|
||||
|
||||
@on('COMMAND-PX')
|
||||
def on_px(canvas, client, x, y, color=None):
|
||||
global pixelcount
|
||||
pixelcount += 1
|
||||
x, y = int(x), int(y)
|
||||
if color:
|
||||
c = int(color, 16)
|
||||
if len(color) == 6:
|
||||
r = (c & 0xff0000) >> 16
|
||||
g = (c & 0x00ff00) >> 8
|
||||
b = c & 0x0000ff
|
||||
a = 0xff
|
||||
elif len(color) == 8:
|
||||
r = (c & 0xff000000) >> 24
|
||||
g = (c & 0x00ff0000) >> 16
|
||||
b = (c & 0x0000ff00) >> 8
|
||||
a = c & 0x000000ff
|
||||
else:
|
||||
return
|
||||
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
|
||||
status_text = ''
|
||||
|
||||
import math
|
||||
|
||||
@on('TICK')
|
||||
def on_tick(canvas):
|
||||
global last_save, pixelcount, status_text
|
||||
|
||||
canvas.text(5, 5, status_text, delay=0)
|
||||
ccount = len([c for c in canvas.clients.values() if c.socket])
|
||||
|
||||
if ccount > 300:
|
||||
for ip, client in canvas.clients.iteritems():
|
||||
client.disconnect()
|
||||
|
||||
if time.time() > last_save:
|
||||
status_text = text
|
||||
status_text += 'px/s: %d \n' % (pixelcount / 5)
|
||||
status_text += 'Connections: %d' % ccount
|
||||
pixelcount = 0
|
||||
|
||||
last_save = time.time() + 5
|
||||
canvas.save_as('save/mov_%d.png' % last_save)
|
||||
|
||||
|
||||
BIN
pixelflut/font.png
Normal file
BIN
pixelflut/font.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
260
pixelflut/pixelflut.py
Executable file
260
pixelflut/pixelflut.py
Executable file
|
|
@ -0,0 +1,260 @@
|
|||
#coding: utf8
|
||||
|
||||
__version__ = '0.6'
|
||||
|
||||
import time
|
||||
from gevent import spawn, sleep as gsleep, GreenletExit
|
||||
from gevent.socket import socket, SOL_SOCKET, SO_REUSEADDR
|
||||
from gevent.coros import Semaphore, RLock
|
||||
from gevent.queue import Queue
|
||||
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
|
||||
|
||||
class Client(object):
|
||||
pps = 1000
|
||||
|
||||
def __init__(self, canvas):
|
||||
self.canvas = canvas
|
||||
self.socket = None
|
||||
self.connect_ts = time.time()
|
||||
# And this is used to limit clients to X messages per tick
|
||||
# We start at 0 (instead of x) to add a reconnect-penalty.
|
||||
self.lock = RLock()
|
||||
|
||||
def send(self, line):
|
||||
with self.lock:
|
||||
if self.socket:
|
||||
self.socket.sendall(line + '\n')
|
||||
|
||||
def nospam(self, line):
|
||||
self.send(line)
|
||||
|
||||
def disconnect(self):
|
||||
with self.lock:
|
||||
if self.socket:
|
||||
socket = self.socket
|
||||
self.socket = None
|
||||
socket.close()
|
||||
self.canvas.fire('DISCONNECT', self)
|
||||
|
||||
def serve(self, socket):
|
||||
self.canvas.fire('CONNECT', self)
|
||||
|
||||
with self.lock:
|
||||
self.socket = socket
|
||||
readline = self.socket.makefile().readline
|
||||
|
||||
try:
|
||||
while self.socket:
|
||||
gsleep(10.0/self.pps)
|
||||
for i in range(10):
|
||||
line = readline(1024).strip()
|
||||
if not line:
|
||||
break
|
||||
arguments = line.split()
|
||||
command = arguments.pop(0)
|
||||
if not self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments):
|
||||
self.disconnect()
|
||||
finally:
|
||||
self.disconnect()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Canvas(object):
|
||||
size = 640, 480
|
||||
flags = pygame.RESIZABLE#|pygame.FULLSCREEN
|
||||
|
||||
def __init__(self):
|
||||
pygame.init()
|
||||
pygame.mixer.quit()
|
||||
self.set_title()
|
||||
self.screen = pygame.display.set_mode(self.size, self.flags)
|
||||
self.frames = 0
|
||||
self.width = self.screen.get_width()
|
||||
self.height = self.screen.get_height()
|
||||
self.clients = {}
|
||||
self.events = {}
|
||||
self.font = pygame.font.Font(None, 17)
|
||||
|
||||
def serve(self, host, port):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
spawn(self._loop)
|
||||
|
||||
self.socket = socket()
|
||||
self.socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||
self.socket.bind((host, port))
|
||||
self.socket.listen(100)
|
||||
while True:
|
||||
sock, addr = self.socket.accept()
|
||||
ip, port = addr
|
||||
|
||||
client = self.clients.get(ip)
|
||||
if client:
|
||||
client.disconnect()
|
||||
client.task.kill()
|
||||
else:
|
||||
client = self.clients[ip] = Client(self)
|
||||
|
||||
client.task = spawn(client.serve, sock)
|
||||
|
||||
def _loop(self):
|
||||
doptim = 1.0 / 30
|
||||
flip = pygame.display.flip
|
||||
getevents = pygame.event.get
|
||||
|
||||
while True:
|
||||
t1 = time.time()
|
||||
|
||||
for e in getevents():
|
||||
if e.type == pygame.VIDEORESIZE:
|
||||
old = self.screen.copy()
|
||||
self.screen = pygame.display.set_mode(e.size, self.flags)
|
||||
self.screen.blit(old, (0,0))
|
||||
self.width, self.height = e.size
|
||||
self.fire('RESIZE')
|
||||
elif e.type == pygame.QUIT:
|
||||
self.fire('QUIT')
|
||||
return
|
||||
elif e.type == pygame.KEYDOWN:
|
||||
self.fire('KEYDOWN-' + e.unicode)
|
||||
|
||||
self.fire('TICK')
|
||||
self.frames += 1
|
||||
|
||||
flip()
|
||||
|
||||
dt = time.time() - t1
|
||||
gsleep(max(doptim-dt, 0))
|
||||
|
||||
def on(self, name):
|
||||
''' If used as a decorator, binds a function to an event. '''
|
||||
def decorator(func):
|
||||
self.events[name] = func
|
||||
return func
|
||||
return decorator
|
||||
|
||||
def fire(self, name, *a, **ka):
|
||||
''' Fire an event. '''
|
||||
if name in self.events:
|
||||
try:
|
||||
self.events[name](self, *a, **ka)
|
||||
return True
|
||||
except GreenletExit:
|
||||
raise
|
||||
except:
|
||||
log.exception('Error in callback for %r', name)
|
||||
|
||||
def get_size(self):
|
||||
''' Get the current screen dimension as a (width, height) tuple.'''
|
||||
return self.width, self.height
|
||||
|
||||
def get_pixel(self, x, y):
|
||||
''' Get colour of a pixel as an (r,g,b) tuple. '''
|
||||
return self.screen.get_at((x,y))
|
||||
|
||||
def set_pixel(self, x, y, r, g, b, a=255):
|
||||
''' Change the colour of a pixel. If an alpha value is given, the new
|
||||
colour is mixed with the old colour accordingly. '''
|
||||
if a == 0:
|
||||
return
|
||||
elif a == 0xff:
|
||||
self.screen.set_at((x, y), (r,g,b))
|
||||
elif 0 <= x < self.width and 0 <= y < self.height:
|
||||
r2, g2, b2, a2 = self.screen.get_at((x, y))
|
||||
r = (r2*(0xff-a)+(r*a)) / 0xff
|
||||
g = (g2*(0xff-a)+(g*a)) / 0xff
|
||||
b = (b2*(0xff-a)+(b*a)) / 0xff
|
||||
self.screen.set_at((x, y), (r,g,b))
|
||||
|
||||
def clear(self, r=0, g=0, b=0, a=255):
|
||||
''' Fill the entire screen with a solid colour (default: black)'''
|
||||
self.screen.fill((r, g, b))
|
||||
|
||||
def save_as(self, filename):
|
||||
''' Save screen to disk. '''
|
||||
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):
|
||||
''' Load a font image with 16x16 sprites. '''
|
||||
self.font_img = pygame.image.load(fname).convert()
|
||||
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):
|
||||
if not self.font_img:
|
||||
self.load_font('font.png')
|
||||
fx = (c%16) * self.font_res
|
||||
fy = (c/16) * self.font_res
|
||||
self.screen.blit(self.font_img, (x,y),
|
||||
(fx,fy,self.font_res,self.font_res))
|
||||
|
||||
def text(self, x, y, text, delay=0, linespace=1):
|
||||
for i, line in enumerate(text.splitlines()):
|
||||
line += ' '
|
||||
for j, c in enumerate(line):
|
||||
self.putc(x+j*self.font_res, y+i*self.font_res, ord(c))
|
||||
gsleep(delay)
|
||||
y += linespace
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
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 = spawn(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()
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue