From 75b436a29aeb70be5aff3726d36aa88d759bd284 Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Wed, 7 May 2014 15:56:04 +0200 Subject: [PATCH] Added java implementation --- README.txt | 28 ++- brain.py => pixelflut/brain.py | 0 font.png => pixelflut/font.png | Bin pixelflut.py => pixelflut/pixelflut.py | 0 pixelwar/pom.xml | 60 ++++++ pixelwar/src/de/paws/pixelwar/Canvas.java | 182 ++++++++++++++++++ .../de/paws/pixelwar/PixelClientHandler.java | 46 +++++ .../src/de/paws/pixelwar/PixelServer.java | 74 +++++++ 8 files changed, 387 insertions(+), 3 deletions(-) rename brain.py => pixelflut/brain.py (100%) rename font.png => pixelflut/font.png (100%) rename pixelflut.py => pixelflut/pixelflut.py (100%) create mode 100644 pixelwar/pom.xml create mode 100644 pixelwar/src/de/paws/pixelwar/Canvas.java create mode 100644 pixelwar/src/de/paws/pixelwar/PixelClientHandler.java create mode 100644 pixelwar/src/de/paws/pixelwar/PixelServer.java 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(); + } + +}