diff --git a/README.txt b/README.txt
index ce3e138..3bff555 100644
--- a/README.txt
+++ b/README.txt
@@ -1,5 +1,27 @@
-Install
-=======
+Pixelflut & Pixelwar
+====================
+
+Multiplayer canvas.
+
+Pixelflut (python)
+------------------
+
+Gevent and pygame based python implementation. easier to hack with, but a bit slow. Not recommended for more than 20 players.
sudo aptitude install python-gevent python-pygame
- python pixelflut.py
+ cd pixelflut
+ python pixelflut.py brain.py
+
+Pixelwar (java)
+---------------
+
+Netty based java7 implementation. Very fast but not scriptable (yet).
+
+ sudo aptitude install maven2 openjdk-7-jdk
+ cd pixelwar
+ mvn package
+ java -jar package/pixelwar*-jar-with-dependencies.jar
+
+
+
+
diff --git a/brain.py b/pixelflut/brain.py
similarity index 100%
rename from brain.py
rename to pixelflut/brain.py
diff --git a/font.png b/pixelflut/font.png
similarity index 100%
rename from font.png
rename to pixelflut/font.png
diff --git a/pixelflut.py b/pixelflut/pixelflut.py
similarity index 100%
rename from pixelflut.py
rename to pixelflut/pixelflut.py
diff --git a/pixelwar/pom.xml b/pixelwar/pom.xml
new file mode 100644
index 0000000..ec7c128
--- /dev/null
+++ b/pixelwar/pom.xml
@@ -0,0 +1,60 @@
+
+ 4.0.0
+ pixelwar
+ pixelwar
+ 0.0.1-SNAPSHOT
+
+ src
+
+
+ maven-compiler-plugin
+ 3.1
+
+ 1.7
+ 1.7
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ de.paws.pixelwar.PixelServer
+
+
+
+
+
+ maven-assembly-plugin
+
+
+
+ de.paws.pixelwar.PixelServer
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
+ io.netty
+ netty-all
+ 4.0.18.Final
+
+
+
\ No newline at end of file
diff --git a/pixelwar/src/de/paws/pixelwar/Canvas.java b/pixelwar/src/de/paws/pixelwar/Canvas.java
new file mode 100644
index 0000000..8eb9047
--- /dev/null
+++ b/pixelwar/src/de/paws/pixelwar/Canvas.java
@@ -0,0 +1,182 @@
+package de.paws.pixelwar;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Transparency;
+import java.awt.event.ComponentEvent;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferStrategy;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JFrame;
+
+public class Canvas {
+ private volatile BufferedImage drawBuffer;
+ private volatile BufferedImage overlayBuffer;
+ private volatile BufferedImage paintBuffer;
+
+ private final BufferStrategy strategy;
+ private final Thread refresher;
+ private final JFrame frame;
+
+ public Canvas() {
+
+ frame = new JFrame() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void processKeyEvent(KeyEvent e) {
+ super.processKeyEvent(e);
+ if (e.getKeyChar() == 'c') {
+ Graphics g = drawBuffer.getGraphics();
+ g.setColor(Color.BLACK);
+ g.fillRect(0, 0, drawBuffer.getWidth(),
+ drawBuffer.getHeight());
+ g.dispose();
+ }
+ }
+
+ @Override
+ protected void processComponentEvent(ComponentEvent e) {
+ super.processComponentEvent(e);
+ if (e.getID() == ComponentEvent.COMPONENT_RESIZED) {
+ resizeBuffer(getWidth(), getHeight());
+ }
+ }
+ };
+
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setUndecorated(true);
+ frame.setResizable(true);
+ frame.setSize(800, 600);
+ frame.setVisible(true);
+ frame.setAlwaysOnTop(true);
+
+ frame.createBufferStrategy(2);
+ strategy = frame.getBufferStrategy();
+ resizeBuffer(1024, 1024);
+
+ refresher = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ draw();
+ Thread.sleep(1000 / 30);
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+ });
+ refresher.start();
+ }
+
+ private void blit(BufferedImage source, BufferedImage target) {
+ Graphics g = target.getGraphics();
+ g.drawImage(source, 0, 0, null);
+ g.dispose();
+ }
+
+ synchronized private void resizeBuffer(int w, int h) {
+ BufferedImage newBuffer;
+ int mw = w, mh = h;
+
+ if (drawBuffer != null) {
+ mw = Math.max(w, drawBuffer.getWidth());
+ mh = Math.max(h, drawBuffer.getHeight());
+ if (mw > w && mh > h)
+ return;
+ }
+
+ newBuffer = frame.getGraphicsConfiguration().createCompatibleImage(w,
+ h, Transparency.OPAQUE);
+ if (drawBuffer != null)
+ blit(drawBuffer, newBuffer);
+ drawBuffer = newBuffer;
+
+ overlayBuffer = frame.getGraphicsConfiguration().createCompatibleImage(
+ w, h, Transparency.TRANSLUCENT);
+
+ paintBuffer = frame.getGraphicsConfiguration().createCompatibleImage(w,
+ h, Transparency.OPAQUE);
+ }
+
+ private void drawOverlay() {
+ Graphics2D g = overlayBuffer.createGraphics();
+ g.setComposite(AlphaComposite.Clear);
+ g.fillRect(0, 0, overlayBuffer.getWidth(), overlayBuffer.getHeight());
+ g.setComposite(AlphaComposite.SrcOver);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g.drawString(String.format("Pixelflut"), 2, 10);
+ g.dispose();
+ }
+
+ synchronized public void draw() {
+ drawOverlay();
+ // Prepare composite buffer
+ Graphics2D g = paintBuffer.createGraphics();
+ g.drawImage(drawBuffer, 0, 0, null);
+ g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
+ g.drawImage(overlayBuffer, 0, 0, null);
+ g.dispose();
+
+ // Blit into double buffer
+ Graphics g2 = strategy.getDrawGraphics();
+ g2.drawImage(paintBuffer, 0, 0, null);
+ g2.dispose();
+ strategy.show();
+ }
+
+ public void setPixel(int x, int y, int argb) {
+ // Below this values 8bit color channels are nulled.
+ BufferedImage img = drawBuffer;
+
+ if (x >= 0 && x < img.getWidth() && y >= 0 && y < img.getHeight()) {
+ int alpha = (argb >>> 24) % 256;
+ int rgb = argb & 0xffffff;
+
+ if (alpha < 1)
+ return;
+
+ if (alpha < 255) {
+ int target = img.getRGB(x, y);
+ int r, g, b;
+
+ r = ((target >>> 16) & 0xff) * (255 - alpha);
+ g = ((target >>> 8) & 0xff) * (255 - alpha);
+ b = ((target >>> 0) & 0xff) * (255 - alpha);
+
+ r += ((argb >>> 16) & 0xff) * alpha;
+ g += ((argb >>> 8) & 0xff) * alpha;
+ b += ((argb >>> 0) & 0xff) * alpha;
+
+ rgb = 0xff << 24;
+ rgb += (r / 255) << 16;
+ rgb += (g / 255) << 8;
+ rgb += (b / 255) << 0;
+ }
+
+ img.setRGB(x, y, rgb);
+ }
+ }
+
+ public int getPixel(int x, int y) {
+ BufferedImage img = drawBuffer;
+ if (x >= 0 && x < img.getWidth() && y >= 0 && y < img.getHeight())
+ return img.getRGB(x, y) & 0x00ffffff;
+ return 0;
+ }
+
+ public int getWidth() {
+ return drawBuffer.getWidth();
+ }
+
+ public int getHeight() {
+ return drawBuffer.getHeight();
+ }
+
+}
diff --git a/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java b/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java
new file mode 100644
index 0000000..5e83848
--- /dev/null
+++ b/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java
@@ -0,0 +1,46 @@
+package de.paws.pixelwar;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+
+public class PixelClientHandler extends SimpleChannelInboundHandler {
+
+ private final Canvas canvas;
+
+ public PixelClientHandler(Canvas canvas) {
+ this.canvas = canvas;
+ }
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, String msg)
+ throws Exception {
+ String[] parts = msg.split(" ");
+ if (parts.length < 1)
+ return;
+
+ String command = parts[0].toUpperCase();
+
+ if (command.equals("PX")) {
+ if (parts.length == 3) {
+ int x = Integer.parseInt(parts[1]);
+ int y = Integer.parseInt(parts[2]);
+ int c = canvas.getPixel(x, y);
+ ctx.write(String.format("PX %d %d %06x\n", x, y, c));
+ ctx.flush();
+ } else if (parts.length == 4) {
+ int x = Integer.parseInt(parts[1]);
+ int y = Integer.parseInt(parts[2]);
+ int color = (int) Long.parseLong(parts[3], 16);
+ if (parts[3].length() == 6)
+ color += 0xff000000;
+ canvas.setPixel(x, y, color);
+ }
+ } else if (command.equals("SIZE")) {
+ ctx.write(String.format("SIZE %d %d\n", canvas.getWidth(),
+ canvas.getHeight()));
+ ctx.flush();
+ } else if (command.equals("TILE") && parts.length == 3) {
+ }
+
+ }
+}
diff --git a/pixelwar/src/de/paws/pixelwar/PixelServer.java b/pixelwar/src/de/paws/pixelwar/PixelServer.java
new file mode 100644
index 0000000..581c8e9
--- /dev/null
+++ b/pixelwar/src/de/paws/pixelwar/PixelServer.java
@@ -0,0 +1,74 @@
+package de.paws.pixelwar;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.DelimiterBasedFrameDecoder;
+import io.netty.handler.codec.Delimiters;
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+
+public class PixelServer extends ChannelHandlerAdapter {
+
+ private final Canvas canvas;
+ private final int port;
+
+ public PixelServer(int port) {
+ this.port = port;
+ this.canvas = new Canvas();
+ }
+
+ public void run() throws InterruptedException {
+ EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+ EventLoopGroup workerGroup = new NioEventLoopGroup();
+ try {
+ ServerBootstrap b = new ServerBootstrap();
+ b.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .option(ChannelOption.SO_BACKLOG, 100)
+ .handler(new LoggingHandler(LogLevel.INFO))
+ .childHandler(new ChannelInitializer() {
+ @Override
+ public void initChannel(SocketChannel ch)
+ throws Exception {
+
+ ch.pipeline().addLast(
+ "framer",
+ new DelimiterBasedFrameDecoder(128,
+ Delimiters.lineDelimiter()));
+ ch.pipeline().addLast("decoder",
+ new StringDecoder());
+ ch.pipeline().addLast("encoder",
+ new StringEncoder());
+
+ // and then business logic.
+ ch.pipeline().addLast("handler",
+ new PixelClientHandler(canvas));
+ }
+ });
+
+ // Start the server.
+ ChannelFuture f = b.bind(port).sync();
+
+ // Wait until the server socket is closed.
+ f.channel().closeFuture().sync();
+ } finally {
+ // Shut down all event loops to terminate all threads.
+ bossGroup.shutdownGracefully();
+ workerGroup.shutdownGracefully();
+ }
+ }
+
+ public static void main(String[] args) throws InterruptedException {
+ new PixelServer(8080).run();
+ }
+
+}