changeset 162:e8eeac403e5f

Backed out changeset fb33d3796942
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 01 Dec 2016 14:33:25 +0200
parents fb33d3796942
children dda7152d2402
files Makefile game/AnimatedPointElement.java game/Engine.java game/G.java game/IDMButton.java game/IDMContainer.java game/IDMPoint.java game/IDMWidget.java game/Interpolate.java game/Piece.java game/PieceType.java game/ResourceLoader.java game/Sound.java game/SoundManager.java src/AnimatedPointElement.java src/Engine.java src/G.java src/IDMButton.java src/IDMContainer.java src/IDMPoint.java src/IDMWidget.java src/Interpolate.java src/Piece.java src/PieceType.java src/ResourceLoader.java src/Sound.java src/SoundManager.java
diffstat 27 files changed, 2451 insertions(+), 2451 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Tue Jun 21 12:53:53 2016 +0300
+++ b/Makefile	Thu Dec 01 14:33:25 2016 +0200
@@ -3,14 +3,14 @@
 
 RESOURCES=graphics/*.png graphics/*.jpg graphics/font.ttf sounds/*.wav
 
-CLASSES=src/G.class \
-	src/Piece.class src/PieceType.class \
-	src/Engine.class src/Interpolate.class \
-	src/ResourceLoader.class \
-	src/Sound.class src/SoundManager.class \
-	src/IDMPoint.class src/IDMWidget.class \
-	src/IDMButton.class src/IDMContainer.class \
-	src/AnimatedPointElement.class
+CLASSES=game/G.class \
+	game/Piece.class game/PieceType.class \
+	game/Engine.class game/Interpolate.class \
+	game/ResourceLoader.class \
+	game/Sound.class game/SoundManager.class \
+	game/IDMPoint.class game/IDMWidget.class \
+	game/IDMButton.class game/IDMContainer.class \
+	game/AnimatedPointElement.class
 
 # Utils
 JAVAC=javac -g -Xlint:unchecked
@@ -23,7 +23,7 @@
 
 all: $(TARGETS)
 
-src/%.class: src/%.java
+game/%.class: game/%.java
 	$(JAVAC) $<
 
 Ristipolku.class: Ristipolku.java $(CLASSES)
@@ -38,7 +38,7 @@
 ### Package
 ###
 Ristipolku.jar: $(RUN) $(RESOURCES)
-	jar cvfm $@ manifest.txt $+ src/*.class
+	jar cvfm $@ manifest.txt $+ game/*.class
 
 
 upload: Ristipolku.jar
@@ -50,10 +50,10 @@
 ### Cleanup
 ###
 clean:
-	$(RM) $(TARGETS) *.class src/*.class
+	$(RM) $(TARGETS) *.class game/*.class
 
 
 srcclean: clean
-	$(RM) *~ src/*~ graphics/*~
+	$(RM) *~ game/*~ graphics/*~
 
 # dummy
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/AnimatedPointElement.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,75 @@
+/*
+ * Ristipolku animated game element
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+import java.math.*;
+
+
+public class AnimatedPointElement
+{
+    private float stime, value, steps;
+    private IDMPoint pos;
+    private Interpolate lerpV, lerpX, lerpY;
+    boolean active, first;
+    String text;
+
+    public AnimatedPointElement(IDMPoint start, String text)
+    {
+        Random rnd = new Random();
+        steps = 100;
+
+        lerpX = new Interpolate(start.x - rnd.nextInt(100) + 50, start.x, steps);
+        lerpY = new Interpolate(start.y - rnd.nextInt(100) + 50, start.y, steps);
+        lerpV = new Interpolate(0.0f, 1.0f, steps);
+
+        pos = new IDMPoint(start);
+        active = false;
+        first = true;
+        this.text = text;
+    }
+
+    public void animate(float time)
+    {
+        if (first)
+        {
+            first = false;
+            active = true;
+            stime = time;
+        }
+
+        if (active)
+        {
+            float t = (time - stime);
+            if (t >= steps)
+            {
+                t = steps;
+                active = false;
+            }
+
+            value = lerpV.getValue(t);
+            pos.x = lerpX.getValue(t);
+            pos.y = lerpY.getValue(t);
+        }
+    }
+
+    public void paint(Graphics2D g)
+    {
+        if (!active)
+            return;
+
+        Paint psave = g.getPaint();
+//        AffineTransform tsave = g.getTransform();
+
+        g.setFont(G.fonts[2]);
+        g.setPaint(new Color(value, value, value, value));
+        g.drawString(text, pos.x, pos.y);
+
+//        g.setTransform(tsave);
+        g.setPaint(psave);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/Engine.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,909 @@
+/*
+ * Ristipolku Game Engine
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import java.awt.font.*;
+import javax.imageio.*;
+import javax.swing.*;
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.io.*;
+import game.*;
+import javax.sound.sampled.*;
+
+
+class AboutBox extends IDMWidget
+{
+    BufferedImage aboutImg;
+    boolean aboutSecret;
+
+    IDMPoint currPos, currOffs;
+    Paint textPaint;
+    Font textFont;
+    FontMetrics textMetrics;
+
+
+    public AboutBox()
+    {
+        try {
+            ResourceLoader res = new ResourceLoader("graphics/girl.jpg");
+            aboutImg = ImageIO.read(res.getStream());
+        }
+        catch (IOException e)
+        {
+        }
+
+        aboutSecret = false;
+
+        setPos(150f, 150f);
+        setSize(675f, 420f);
+    }
+
+    public void setTextFont(Font font, FontMetrics metrics)
+    {
+        textFont = font;
+        textMetrics = metrics;
+    }
+
+    public void setTextPaint(Paint paint)
+    {
+        textPaint = paint;
+    }
+
+    public void setCurrPos(IDMPoint npos)
+    {
+        currPos = npos;
+        currOffs = new IDMPoint(0, 0);
+    }
+
+    public void setCurrPos(float x, float y)
+    {
+        setCurrPos(new IDMPoint(x, y));
+    }
+
+    public void drawString(Graphics2D g, String text)
+    {
+        Paint savePaint = g.getPaint();
+        g.setPaint(textPaint);
+        g.setFont(textFont);
+
+        int i = 0;
+        while (i < text.length())
+        {
+            int p = text.indexOf("\n", i);
+            boolean linefeed;
+            String str;
+            if (p >= i)
+            {
+                str = text.substring(i, p);
+                i = p + 1;
+                linefeed = true;
+            }
+            else
+            {
+                str = text.substring(i);
+                i += str.length();
+                linefeed = false;
+            }
+
+            g.drawString(str, currPos.x + currOffs.x, currPos.y + currOffs.y);
+
+            if (linefeed)
+            {
+                currOffs.x = 0;
+                currOffs.y += textMetrics.getHeight();
+            }
+            else
+            {
+                currOffs.x += textMetrics.stringWidth(str);
+            }
+        }
+
+        g.setPaint(savePaint);
+    }
+
+    public void paint(Graphics2D g)
+    {
+        int x = getScaledX(), y = getScaledY(),
+            w = getScaledWidth(), h = getScaledHeight();
+
+        g.setPaint(new Color(0.0f, 0.0f, 0.0f, 0.7f));
+        g.fill(new RoundRectangle2D.Float(x, y, w, h, 10, 10));
+
+        setTextFont(G.fonts[3], G.metrics[3]);
+        setTextPaint(Color.white);
+
+        setCurrPos(x + 20, y + 30);
+        drawString(g, "RistiPolku (CrossPaths) v"+ G.version +"\n");
+
+        setTextFont(G.fonts[1], G.metrics[1]);
+        if (aboutSecret)
+        {
+            g.drawImage(aboutImg, x + 20, y + 55,
+                aboutImg.getWidth(), aboutImg.getHeight(), null);
+
+            setCurrPos(x + 225, y + 75);
+            drawString(g,
+                "Dedicated to my\n" +
+                "favorite woman\n" +
+                "in the world.");
+
+            setCurrPos(x + 370, y + 175);
+            drawString(g, "- Matti");
+        }
+        else
+        {
+            setTextPaint(Color.yellow);
+            drawString(g, "(c) Copyright 2011 Tecnic Software productions (TNSP)\n");
+
+            setTextPaint(Color.white);
+            drawString(g, "Programming, graphics and design by " +
+                          "Matti 'ccr' Hämäläinen.\n" +
+                          "Audio from archive.org, used non-commercially.\n \n");
+
+            setTextPaint(Color.red);
+            drawString(g, "Controls:\n");
+
+            IDMPoint old = currOffs.copy();
+
+            setTextPaint(Color.white);
+            drawString(g,
+            "Arrow keys / mouse wheel\n"+
+            "Enter / mouse click\n"+
+            "Space bar\n");
+
+            currPos.x += 330;
+            currOffs.y = old.y;
+            drawString(g,
+            "- Rotate piece\n" +
+            "- Place/lock piece\n" +
+            "- Swap piece\n");
+
+            currPos.x -= 330;
+            setTextPaint(Color.green);
+            drawString(g,
+            "\nObjective: Create a path long as possible by rotating\n"+
+            "and placing pieces. More points will be awarded for\n"+
+            "advancing the path by several segments per turn."
+            );
+        }
+    }
+
+    public boolean keyPressed(KeyEvent e)
+    {
+        if (e.getKeyCode() == KeyEvent.VK_L)
+        {
+            aboutSecret = true;
+        }
+        else
+        {
+            clicked();
+            aboutSecret = false;
+        }
+        return true;
+    }
+
+    public void clicked()
+    {
+        parent.remove(this);
+    }
+}
+
+
+class GameBoard extends IDMWidget
+{
+    static final int boardSize = 9;
+    static final int boardMiddle = 4;
+    Piece[][] board;
+    float pscale, ptime;
+
+    public boolean flagGameOver, flagBoardIsDirty;
+    int gameScore;
+
+    Piece currPiece, nextPiece;
+    int currX, currY, currPoint;
+
+    Sound sndPlaced;
+
+    private final ReentrantReadWriteLock pointLock = new ReentrantReadWriteLock();
+    private ArrayList<AnimatedPointElement> pointElems;
+
+    public GameBoard(IDMPoint pos, float ps)
+    {
+        super(pos);
+        pscale = ps;
+
+//        sndPlaced = G.smgr.getSound("sounds/placed.wav");
+
+        pointElems = new ArrayList<AnimatedPointElement>();
+
+        startNewGame();
+    }
+
+    public void startNewGame()
+    {
+        board = new Piece[boardSize][boardSize];
+        board[boardMiddle][boardMiddle] = new Piece(PieceType.START);
+
+        currX = boardMiddle;
+        currY = boardMiddle;
+        currPoint = 0;
+
+        currPiece = null;
+        nextPiece = new Piece(PieceType.ACTIVE);
+
+        flagGameOver = false;
+        flagBoardIsDirty = true;
+        pieceFinishTurn();
+        gameScore = 0;
+    }
+
+    public void paintBackPlate(Graphics2D g)
+    {
+        g.setPaint(new Color(0.0f, 0.0f, 0.0f, 0.2f));
+        g.setStroke(new BasicStroke(5.0f));
+        g.draw(new RoundRectangle2D.Float(getScaledX(), getScaledY(),
+            boardSize * pscale, boardSize * pscale,
+            pscale / 5, pscale / 5));
+    }
+
+    public void paintBoard(Graphics2D g, boolean drawCurrent)
+    {
+        for (int y = 0; y < boardSize; y++)
+        for (int x = 0; x < boardSize; x++)
+        if (board[x][y] != null)
+        {
+            if ((drawCurrent && board[x][y] == currPiece) ||
+                (!drawCurrent && board[x][y] != currPiece))
+            {
+                board[x][y].paint(g,
+                    getScaledX() + (x * pscale),
+                    getScaledY() + (y * pscale),
+                    pscale - pscale / 10);
+            }
+        }
+    }
+
+    public void paint(Graphics2D g)
+    {
+        paintBoard(g, true);
+
+        Lock read = pointLock.readLock();
+        read.lock();
+        try
+        {
+            for (AnimatedPointElement elem : pointElems)
+            {
+                elem.paint(g);
+            }
+        }
+        finally
+        {
+            read.unlock();
+        }
+
+
+        if (!flagGameOver)
+        {
+            if (nextPiece != null)
+            {
+                // Draw next piece
+                AffineTransform save = g.getTransform();
+                nextPiece.paint(g, G.screenDim.width * 0.85f - 90.0f/2.0f, G.screenDim.height * 0.43f, 90.0f);
+                g.setTransform(save);
+            }
+        }
+        else
+        {
+            // Game over text
+            String text = "Game Over!";
+            int textWidth = G.metrics[2].stringWidth(text);
+            g.setFont(G.fonts[2]);
+
+            g.setPaint(new Color(0.0f, 0.0f, 0.0f, 0.5f));
+            g.drawString(text, (G.screenDim.width - textWidth) / 2 + 5, G.screenDim.height / 2 + 5);
+
+            double f = Math.sin(ptime * 0.1) * 4.0;
+            g.setPaint(Color.white);
+            g.drawString(text, (G.screenDim.width - textWidth) / 2 + (float) f, G.screenDim.height / 2 + (float) f);
+        }
+
+        // Score
+        String text = ""+ String.format("%05d", gameScore);
+        int textWidth = G.metrics[2].stringWidth(text);
+        g.setFont(G.fonts[2]);
+        g.setPaint(Color.white);
+        g.drawString(text, (G.screenDim.width * 0.85f) - textWidth / 2, G.screenDim.height * 0.3f);
+    }
+
+    public boolean contains(float x, float y)
+    {
+        return (x >= getScaledX() &&
+                y >= getScaledY() &&
+                x < getScaledX() + boardSize * pscale &&
+                y < getScaledY() + boardSize * pscale);
+    }
+
+    public boolean isBoardDirty()
+    {
+        if (flagBoardIsDirty)
+        {
+            flagBoardIsDirty = false;
+            return true;
+        }
+        else
+            return false;
+    }
+
+    public void animate(float time)
+    {
+        if (nextPiece != null)
+        {
+            nextPiece.animate(time);
+        }
+            
+        ptime = time;
+        for (int y = 0; y < boardSize; y++)
+        for (int x = 0; x < boardSize; x++)
+        if (board[x][y] != null)
+        {
+            board[x][y].animate(time);
+            if (board[x][y] != currPiece && board[x][y].active)
+                flagBoardIsDirty = true;
+        }
+
+        Lock write = pointLock.writeLock();
+        write.lock();
+        try
+        {
+            ArrayList<AnimatedPointElement> tmp = new ArrayList<AnimatedPointElement>();
+
+            for (AnimatedPointElement elem : pointElems)
+            {
+                elem.animate(time);
+                if (elem.active)
+                    tmp.add(elem);
+            }
+
+            pointElems = tmp;
+
+            if (time % 32 == 1)
+            {
+                Random rnd = new Random();
+                pointElems.add(new AnimatedPointElement(
+                    new IDMPoint(10 + rnd.nextInt(400), 10 + rnd.nextInt(100)), "."));
+            }
+        }
+        finally
+        {
+            write.unlock();
+        }
+    }
+
+    public void pieceRotate(Piece.RotateDir dir)
+    {
+        if (currPiece != null && !flagGameOver)
+        {
+            currPiece.rotate(dir);
+        }
+    }
+
+    // Change coordinates based on the "outgoing"
+    // piece connection point.
+    private void pieceMoveTo(int point)
+    {
+        switch (point)
+        {
+            case 0: currY--; break;
+            case 1: currY--; break;
+
+            case 2: currX++; break;
+            case 3: currX++; break;
+
+            case 4: currY++; break;
+            case 5: currY++; break;
+
+            case 6: currX--; break;
+            case 7: currX--; break;
+        }
+    }
+
+    public void pieceCreateNew()
+    {
+        currPiece = nextPiece;
+        currPiece.changed();
+        nextPiece = new Piece(PieceType.ACTIVE);
+        flagBoardIsDirty = true;
+    }
+
+    public void pieceSwapCurrent()
+    {
+        if (!flagGameOver)
+        {
+            Piece tmp = currPiece;
+            currPiece = nextPiece;
+            nextPiece = tmp;
+            board[currX][currY] = currPiece;
+            currPiece.changed();
+            nextPiece.changed();
+            flagBoardIsDirty = true;
+        }
+    }
+
+    // Check one piece, set connections, find the new placement
+    // based on piece rotations etc.
+    private boolean pieceCheck(Piece piece)
+    {
+        if (piece == null)
+        {
+            // Create new piece
+            pieceCreateNew();
+            board[currX][currY] = currPiece;
+            return true;
+        }
+        else
+        if (piece.getType() == PieceType.START)
+        {
+            if (currPiece != null)
+            {
+                // Hit center starting piece, game over
+                flagGameOver = true;
+                return true;
+            }
+            else
+            {
+                // Start piece as first piece means game is starting
+                pieceMoveTo(currPoint);
+                pieceCreateNew();
+                board[currX][currY] = currPiece;
+                return true;
+            }
+        }
+
+        // Mark the current piece as locked
+        piece.setType(PieceType.LOCKED);
+
+        // Solve connection (with rotations) through the piece
+        currPoint = piece.getRotatedPoint(piece.getMatchingPoint(currPoint));
+
+        // Mark connection as active
+        piece.setConnectionState(currPoint, true);
+
+        // Solve exit point (with rotations)
+        currPoint = piece.getAntiRotatedPoint(piece.getConnection(currPoint));
+
+        // Move to next position accordingly
+        pieceMoveTo(currPoint);
+        return false;
+    }
+
+    // Finish one move/turn of the game, resolve path and find placement
+    // of the next piece, or set "game over" state if required.
+    public void pieceFinishTurn()
+    {
+        boolean finished = false;
+        int connections = 0;
+
+        if (currPiece != null)
+        {
+            G.smgr.play(sndPlaced);
+        }
+
+        while (!finished)
+        {
+            if (currX >= 0 && currX < boardSize && currY >= 0 && currY < boardSize)
+            {
+                int oldX = currX, oldY = currY;
+                connections++;
+                finished = pieceCheck(board[currX][currY]);
+
+                if (!finished)
+                {
+                    Lock write = pointLock.writeLock();
+                    write.lock();
+                    try
+                    {
+                        pointElems.add(new AnimatedPointElement(
+                            new IDMPoint(
+                            getScaledX() + ((oldX + 0.5f) * pscale),
+                            getScaledY() + ((oldY + 0.5f) * pscale)),
+                            "" + connections));
+                    }
+                    finally
+                    {
+                        write.unlock();
+                    }
+                }
+
+            }
+            else
+            {
+                // Outside of the board, game over
+                finished = true;
+                flagGameOver = true;
+            }
+        }
+
+        // Compute and add score
+        gameScore += connections * connections;
+
+        // If game over, clear the game
+        if (flagGameOver)
+        {
+            currPiece = null;
+        }
+
+        flagBoardIsDirty = true;
+    }
+
+    public boolean mouseWheelMoved(MouseWheelEvent e)
+    {
+        int notches = e.getWheelRotation();
+
+        if (notches < 0)
+            pieceRotate(Piece.RotateDir.LEFT);
+        else
+            pieceRotate(Piece.RotateDir.RIGHT);
+
+        return true;
+    }
+
+    public void clicked()
+    {
+        if (!flagGameOver)
+            pieceFinishTurn();
+    }
+
+    public boolean keyPressed(KeyEvent e)
+    {
+        if (flagGameOver)
+            return false;
+
+        switch (e.getKeyCode())
+        {
+            case KeyEvent.VK_LEFT:
+            case KeyEvent.VK_UP:
+                pieceRotate(Piece.RotateDir.LEFT);
+                return true;
+
+            case KeyEvent.VK_RIGHT:
+            case KeyEvent.VK_DOWN:
+                pieceRotate(Piece.RotateDir.RIGHT);
+                return true;
+
+            case KeyEvent.VK_ENTER:
+                pieceFinishTurn();
+                return true;
+        }
+        return false;
+    }
+}
+
+
+public class Engine extends JPanel
+                    implements Runnable, KeyListener,
+                    MouseListener, MouseWheelListener
+{
+    long startTime;
+    float gameUpdates, gameFrames;
+
+    Thread animThread;
+    boolean animEnable = false;
+
+    GameBoard lauta = null;
+    InputStream musa;
+    IDMContainer widgets;
+    AboutBox aboutBox;
+
+    public void dbg(String msg)
+    {
+        System.out.print("Engine: " + msg);
+    }
+
+    public Engine()
+    {
+        // Initialize globals
+        System.out.print("Engine() constructor\n");
+
+        // Sound system
+        G.smgr = new SoundManager(new AudioFormat(22050, 16, 1, true, false), 1);
+
+        // Load resources
+        try
+        {
+            ResourceLoader res = new ResourceLoader("graphics/board.jpg");
+            G.lautaBG = ImageIO.read(res.getStream());
+
+            try {
+                res = new ResourceLoader("graphics/font.ttf");
+
+                G.fonts = new Font[G.numFonts];
+                G.fonts[0] = Font.createFont(Font.TRUETYPE_FONT, res.getStream());
+                G.fonts[1] = G.fonts[0].deriveFont(24f);
+                G.fonts[2] = G.fonts[0].deriveFont(64f);
+                G.fonts[3] = G.fonts[0].deriveFont(32f);
+            }
+            catch (FontFormatException e)
+            {
+                dbg("Could not initialize fonts.\n");
+            }
+
+            res = new ResourceLoader("sounds/gamemusic.wav");
+            musa = res.getStream();
+        }
+        catch (IOException e)
+        {
+            JOptionPane.showMessageDialog(null,
+                e.getMessage(),
+                "Initialization error",
+                JOptionPane.ERROR_MESSAGE);
+
+            dbg(e.getMessage());
+        }
+
+        // Create IDM GUI widgets
+        widgets = new IDMContainer();
+
+        lauta = new GameBoard(new IDMPoint(95, 130), 63);
+        widgets.add(lauta);
+
+        widgets.add(new BtnSwapPiece(767f, 450f));
+        widgets.add(new BtnAbout    (767f, 550f));
+        widgets.add(new BtnNewGame  (767f, 630f));
+
+        aboutBox = new AboutBox();
+
+        // Game
+        startNewGame();
+
+        // Initialize event listeners
+        addKeyListener(this);
+        addMouseListener(this);
+        addMouseWheelListener(this);
+
+        // Start playing background music
+        G.smgr.play(musa);
+
+        // Get initial focus
+        if (!hasFocus())
+        {
+            dbg("Requesting focus.\n");
+            requestFocus();
+        }
+
+        gameUpdates = 0;
+    }
+
+    public void startNewGame()
+    {
+        gameFrames = 0;
+        startTime = new Date().getTime();
+        lauta.startNewGame();
+    }
+
+    public void paintComponent(Graphics g)
+    {
+        Graphics2D g2 = (Graphics2D) g;
+        boolean scaleChanged = false,
+                updateBoard = lauta.isBoardDirty();
+
+        // Rescale if parent component size has changed
+        Dimension dim = getSize();
+        if (G.screenDim == null || !dim.equals(G.screenDim))
+        {
+            float dw = dim.width / 1024.0f,
+                  dh = dim.height / 768.0f;
+
+            // Rescale IDM GUI widgets
+            widgets.setScale(dw, dh);
+            G.screenDim = dim;
+
+            // Rescale background image
+            // Rescale fonts
+            G.fonts[1] = G.fonts[0].deriveFont(24f * dw);
+            G.fonts[2] = G.fonts[0].deriveFont(64f * dw);
+            G.fonts[3] = G.fonts[0].deriveFont(32f * dw);
+
+            dbg("Scale changed.\n");
+            scaleChanged = true;
+            updateBoard = true;
+        }
+        
+        if (updateBoard)
+        {
+//            dbg("updateBoard()\n");
+            G.lautaBGScaled = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_RGB);
+            Graphics2D gimg = G.lautaBGScaled.createGraphics();
+            gimg.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                  RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+
+            gimg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                RenderingHints.VALUE_ANTIALIAS_ON);
+
+            gimg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+            gimg.drawImage(G.lautaBG, 0, 0, dim.width, dim.height, null);
+            lauta.paintBackPlate(gimg);
+            lauta.paintBoard(gimg, false);
+        }
+
+        // Get font metrics against current Graphics2D context
+        if (G.metrics == null || scaleChanged)
+        {
+            G.metrics = new FontMetrics[G.numFonts];
+            for (int i = 0; i < G.numFonts; i++)
+                G.metrics[i] = g2.getFontMetrics(G.fonts[i]);
+        }
+
+        // Draw background image, pieces, widgets
+        g2.drawImage(G.lautaBGScaled, 0, 0, null);
+
+        // Use antialiasing when rendering the game elements
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                            RenderingHints.VALUE_ANTIALIAS_ON);
+
+        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+        widgets.paint(g2);
+
+
+        // Frames per second counter
+/*
+        g2.setFont(G.fonts[1]);
+        long currTime = new Date().getTime();
+        g2.drawString("fps = "+ ((gameFrames * 1000) / (currTime - startTime)), G.screenDim.width - 120, 20);
+*/
+        gameFrames++;
+    }
+
+    public void startThreads()
+    {
+        dbg("startThreads()\n");
+        if (animThread == null)
+        {
+            animThread = new Thread(this);
+            animEnable = true;
+            animThread.start();
+        }
+    }
+
+    public void stopThreads()
+    {
+        dbg("stopThreads()\n");
+
+        // Stop animations
+        if (animThread != null)
+        {
+            animThread.interrupt();
+            animEnable = false;
+            animThread = null;
+        }
+
+        // Shut down sound manager
+        G.smgr.close();
+    }
+
+    public void mouseEntered(MouseEvent e)
+    {
+        widgets.mouseEntered(e);
+    }
+    public void mouseExited(MouseEvent e)
+    {
+        widgets.mouseExited(e);
+    }
+
+    public void mousePressed(MouseEvent e)
+    {
+        if (widgets.containsObject(aboutBox))
+            aboutBox.mousePressed(e);
+        else
+            widgets.mousePressed(e);
+    }
+
+    public void mouseReleased(MouseEvent e)
+    {
+        if (widgets.containsObject(aboutBox))
+            aboutBox.mouseReleased(e);
+        else
+            widgets.mouseReleased(e);
+    }
+
+    public void mouseClicked(MouseEvent e)
+    {
+        if (!hasFocus())
+        {
+            dbg("Requesting focus.\n");
+            requestFocus();
+        }
+    }
+
+    public void mouseWheelMoved(MouseWheelEvent e)
+    {
+        lauta.mouseWheelMoved(e);
+    }
+
+    public void keyTyped(KeyEvent e) { }
+    public void keyReleased(KeyEvent e) { }
+
+    public void keyPressed(KeyEvent e)
+    {
+        // Handle keyboard input
+        if (widgets.containsObject(aboutBox))
+            aboutBox.keyPressed(e);
+        else
+            widgets.keyPressed(e);
+    }
+
+    public void run()
+    {
+        while (animEnable)
+        {
+            // Progress game animation clock
+            gameUpdates++;
+
+            // Animate components
+            lauta.animate(gameUpdates);
+
+            // Repaint with a frame limiter
+            if (gameUpdates % 4 == 1)
+                repaint();
+
+            // Sleep for a moment
+            try {
+                Thread.sleep(10);
+            }
+            catch (InterruptedException x) {
+            }
+        }
+    }
+
+    class BtnNewGame extends IDMButton
+    {
+        public BtnNewGame(float x, float y)
+        {
+            super(x, y, KeyEvent.VK_ESCAPE, G.fonts[1], "New Game");
+        }
+
+        public void clicked()
+        {
+            startNewGame();
+        }
+    }
+
+    class BtnSwapPiece extends IDMButton
+    {
+        public BtnSwapPiece(float x, float y)
+        {
+            super(x, y, KeyEvent.VK_SPACE, G.fonts[1], "Swap");
+        }
+
+        public void clicked()
+        {
+            lauta.pieceSwapCurrent();
+        }
+    }
+
+    class BtnAbout extends IDMButton
+    {
+        public BtnAbout(float x, float y)
+        {
+            super(x, y, KeyEvent.VK_A, G.fonts[1], "About");
+        }
+
+        public void clicked()
+        {
+            if (!widgets.containsObject(aboutBox))
+                widgets.add(aboutBox);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/G.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,29 @@
+package game;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import java.awt.font.*;
+import javax.imageio.*;
+import javax.swing.*;
+import java.util.*;
+import java.io.*;
+import game.*;
+import javax.sound.sampled.*;
+
+public class G
+{
+    public static final String version = "0.85";
+
+    public static final int numFonts = 4;
+    public static Font fonts[];
+    public static FontMetrics metrics[];
+
+    public static SoundManager smgr;
+
+    public static Dimension screenDim;
+
+    public static BufferedImage lautaBG = null, lautaBGScaled = null;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/IDMButton.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,134 @@
+/*
+ * Ristipolku IDM button widget
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.awt.geom.*;
+import javax.imageio.*;
+import java.io.*;
+
+
+public class IDMButton extends IDMWidget
+{
+    enum State { FOCUSED, PRESSED, NORMAL }
+    State state;
+    static BufferedImage imgUp, imgPressed;
+    Font font;
+    FontMetrics metrics;
+    String text;
+    boolean active;
+
+    public IDMButton(IDMPoint pos, int keyCode, Font font, String text)
+    {
+        super(pos);
+        loadImages();
+        setSize(imgUp.getWidth(), imgUp.getHeight());
+
+        this.font = font;
+        this.keyCode = keyCode;
+        this.text = text;
+
+        state = State.NORMAL;
+        active = false;
+    }
+
+    public IDMButton(float x, float y, int keyCode, Font font, String text)
+    {
+        this(new IDMPoint(x, y), keyCode, font, text);
+    }
+
+    private static void loadImages()
+    {
+        if (imgUp != null && imgPressed != null)
+            return;
+
+        try
+        {
+            ResourceLoader res = new ResourceLoader("graphics/button1_up.png");
+            imgUp = ImageIO.read(res.getStream());
+
+            res = new ResourceLoader("graphics/button1_down.png");
+            imgPressed = ImageIO.read(res.getStream());
+        }
+        catch (IOException e)
+        {
+            System.out.print(e.getMessage());
+        }
+    }
+
+    public void scale()
+    {
+    }
+
+    public void paint(Graphics2D g)
+    {
+        BufferedImage img;
+        int xoffs, yoffs;
+
+        if (state == State.PRESSED)
+        {
+            img = imgPressed;
+            xoffs = yoffs = 5;
+        }
+        else
+        {
+            xoffs = yoffs = 0;
+            img = imgUp;
+        }
+
+        if (metrics == null)
+            metrics = g.getFontMetrics(font);
+
+        int textWidth = metrics.stringWidth(text);
+        g.drawImage(img, getScaledX() + xoffs, getScaledY() + yoffs, null);
+
+        g.setFont(font);
+        g.setPaint(Color.black);
+        g.drawString(text,
+           getScaledX() + xoffs + (getScaledWidth() - textWidth) / 2,
+           getScaledY() + yoffs + getScaledHeight() / 2);
+    }
+
+
+    public boolean mousePressed(MouseEvent e)
+    {
+        state = State.PRESSED;
+        active = true;
+        return true;
+    }
+
+    public boolean mouseReleased(MouseEvent e)
+    {
+        boolean oldActive = active;
+        super.mouseReleased(e);
+        state = State.NORMAL;
+        active = false;
+        return oldActive;
+    }
+
+    public boolean mouseEntered(MouseEvent e)
+    {
+        if (active)
+        {
+            state = State.PRESSED;
+            return true;
+        }
+        else
+            return false;
+    }
+
+    public boolean mouseExited(MouseEvent e)
+    {
+        if (active)
+        {
+            state = State.NORMAL;
+            return true;
+        }
+        else
+            return false;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/IDMContainer.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,190 @@
+/*
+ * Ristipolku IDM widget container
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+
+
+public class IDMContainer extends IDMWidget
+{
+    private ArrayList<IDMWidget> children, queue;
+    private IDMWidget modal;
+    private int iterated;
+    private boolean modified;
+
+    public IDMContainer()
+    {
+        children = new ArrayList<IDMWidget>();
+        iterated = 0;
+        modified = false;
+    }
+
+    synchronized public void add(IDMWidget widget)
+    {
+        widget.setParent(this);
+        if (iterated > 0)
+        {
+            queue.add(0, widget);
+            modified = true;
+        }
+        else
+            children.add(widget);
+    }
+
+    synchronized public void remove(IDMWidget widget)
+    {
+        widget.setParent(null);
+        if (iterated > 0)
+        {
+            queue.remove(widget);
+            modified = true;
+        }
+        else
+            children.remove(widget);
+    }
+
+    synchronized private void beginIteration()
+    {
+        modified = false;
+        if (iterated == 0)
+        {
+            queue = new ArrayList<IDMWidget>();
+            for (IDMWidget widget : children)
+                queue.add(widget);
+        }
+        iterated++;
+    }
+
+    synchronized private void endIteration()
+    {
+        if (modified && iterated == 1)
+        {
+            children = queue;
+            queue = null;
+        }
+        iterated--;
+    }
+
+    synchronized public void paint(Graphics2D g)
+    {
+        // Paint in reverse order
+        ListIterator<IDMWidget> iter = children.listIterator(children.size());
+
+        while (iter.hasPrevious())
+        {
+            final IDMWidget widget = iter.previous();
+            widget.paint(g);
+        }
+    }
+
+    synchronized public boolean mousePressed(MouseEvent e)
+    {
+        try {
+            beginIteration();
+            for (IDMWidget widget : children)
+            {
+                if (widget.contains(e.getPoint()))
+                {
+                    if (widget.mousePressed(e))
+                        return true;
+                }
+            }
+        }
+        finally { endIteration(); }
+        return false;
+    }
+
+    synchronized public boolean mouseReleased(MouseEvent e)
+    {
+        try {
+            beginIteration();
+            for (IDMWidget widget : children)
+            {
+                if (widget.mouseReleased(e))
+                    return true;
+            }
+        }
+        finally { endIteration(); }
+        return false;
+    }
+
+    synchronized public boolean mouseExited(MouseEvent e)
+    {
+        try {
+            beginIteration();
+            for (IDMWidget widget : children)
+            {
+                if (widget.mouseExited(e))
+                    return true;
+            }
+        }
+        finally { endIteration(); }
+        return false;
+    }
+
+    synchronized public boolean mouseEntered(MouseEvent e)
+    {
+        try {
+            beginIteration();
+            for (IDMWidget widget : children)
+            {
+                if (widget.contains(e.getPoint()))
+                {
+                    if (widget.mouseEntered(e))
+                        return true;
+                }
+            }
+        }
+        finally { endIteration(); }
+        return false;
+    }
+
+    synchronized public boolean keyPressed(KeyEvent e)
+    {
+        try {
+            beginIteration();
+            for (IDMWidget widget : children)
+            {
+                 if (widget.keyPressed(e))
+                    return true;
+            }
+        }
+        finally { endIteration(); }
+        return false;
+    }
+
+    synchronized public void setScale(IDMPoint scale)
+    {
+        beginIteration();
+        for (IDMWidget widget : children)
+        {
+            widget.setScale(scale);
+        }
+        endIteration();
+    }
+
+    synchronized public boolean hasObject(Class c)
+    {
+        for (IDMWidget widget : children)
+        {
+            if (widget.getClass() == c)
+                return true;
+        }
+        return false;
+    }
+
+    synchronized public boolean containsObject(Object o)
+    {
+        for (IDMWidget widget : children)
+        {
+            if (widget == o)
+                return true;
+        }
+        return false;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/IDMPoint.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,27 @@
+/*
+ * Ristipolku IDM point
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+public class IDMPoint
+{
+    public float x, y;
+
+    public IDMPoint(float x, float y)
+    {
+        this.x = x;
+        this.y = y;
+    }
+
+    public IDMPoint(IDMPoint pt)
+    {
+        this.x = pt.x;
+        this.y = pt.y;
+    }
+
+    public IDMPoint copy()
+    {
+        return new IDMPoint(x, y);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/IDMWidget.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,163 @@
+/*
+ * Ristipolku IDM base widget
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.awt.*;
+import java.awt.event.*;
+
+
+public class IDMWidget
+{
+    IDMWidget parent;
+    IDMPoint pos, size, scale;
+    int keyCode;
+
+    public IDMWidget()
+    {
+        keyCode = -1;
+        this.scale = new IDMPoint(1, 1);
+        this.pos = new IDMPoint(0, 0);
+        this.size = new IDMPoint(0, 0);
+    }
+
+    public IDMWidget(IDMPoint pos)
+    {
+        this();
+        this.pos = pos;
+    }
+
+    public IDMWidget(IDMPoint pos, IDMPoint size)
+    {
+        this();
+        this.pos = pos;
+        this.size = size;
+    }
+
+    public void setParent(IDMWidget par)
+    {
+        this.parent = par;
+    }
+
+    public void add(IDMWidget widget)
+    {
+    }
+
+    public void remove(IDMWidget widget)
+    {
+    }
+
+    public void setPos(IDMPoint pos)
+    {
+        this.pos = pos;
+    }
+
+    public void setPos(float x, float y)
+    {
+        this.pos = new IDMPoint(x, y);
+    }
+
+    public void setSize(IDMPoint size)
+    {
+        this.size = size;
+    }
+
+    public void setSize(float w, float h)
+    {
+        this.size = new IDMPoint(w, h);
+    }
+
+    public void setScale(IDMPoint scale)
+    {
+        this.scale = scale;
+    }
+
+    public void setScale(float x, float y)
+    {
+        this.setScale(new IDMPoint(x, y));
+    }
+
+    public int getScaledX()
+    {
+        return (int) (pos.x * scale.x);
+    }
+
+    public int getScaledY()
+    {
+        return (int) (pos.y * scale.y);
+    }
+
+    public int getScaledWidth()
+    {
+        return (int) (size.x * scale.x);
+    }
+
+    public int getScaledHeight()
+    {
+        return (int) (size.y * scale.y);
+    }
+
+    public boolean contains(float x, float y)
+    {
+        return (x >= getScaledX() &&
+                y >= getScaledY() &&
+                x < getScaledX() + getScaledWidth() &&
+                y < getScaledY() + getScaledHeight());
+    }
+
+    public boolean contains(Point where)
+    {
+        return contains(where.x, where.y);
+    }
+
+    public boolean contains(IDMPoint where)
+    {
+        return contains(where.x, where.y);
+    }
+
+    public void paint(Graphics2D g)
+    {
+    }
+
+    public boolean mousePressed(MouseEvent e)
+    {
+        return false;
+    }
+
+    public boolean mouseReleased(MouseEvent e)
+    {
+        if (contains(e.getPoint()))
+        {
+            clicked();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean mouseEntered(MouseEvent e)
+    {
+        return false;
+    }
+
+    public boolean mouseExited(MouseEvent e)
+    {
+        return false;
+    }
+
+    // Generic key handler
+    public boolean keyPressed(KeyEvent e)
+    {
+        if (e.getKeyCode() == keyCode)
+        {
+            clicked();
+            return true;
+        }
+        else
+            return false;
+    }
+
+    public void clicked()
+    {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/Interpolate.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,26 @@
+/*
+ * Class for smooth non-linear interpolation between two given values in N steps
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.util.*;
+
+public class Interpolate
+{
+    public float start, end, steps;
+
+    public Interpolate(float start, float end, float steps)
+    {
+        this.start = start;
+        this.end = end;
+        this.steps = steps;
+    }
+
+    public float getValue(float step)
+    {
+        float n = step / steps;
+        float v = n * n * (3.0f - 2.0f * n);
+        return (start * v) + (end * (1.0f - v));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/Piece.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,365 @@
+/*
+ * Ristipolku
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+import java.math.*;
+
+
+public class Piece
+{
+    public enum RotateDir { LEFT, RIGHT }
+
+    static final int numConnections = 8;
+    static final float maxTime = 50.0f;
+
+    int currRotation;
+    int[] connections;
+    boolean[] states;
+    PieceType type, prevType;
+
+    boolean rotationChanged, rotationActive,
+            typeChanged, typeActive,
+            stateChanged, stateActive,
+            active;
+
+    float   currAngle, rotationTime, rotationSpeed,
+            typeTime, typeValue, throbTime;
+
+    Interpolate lerpRotation,
+                lerpScale,
+                lerpType;
+
+
+    public Piece(PieceType ptype)
+    {
+        // Initialize
+        connections = new int[numConnections];
+        states = new boolean[numConnections];
+        type = ptype;
+
+        rotationChanged = false;
+        rotationActive = false;
+        currRotation = 4 * 5000;
+        currAngle = getAngle(currRotation);
+
+        typeChanged = false;
+        typeActive = false;
+
+        throbTime = 0;
+
+        lerpType  = new Interpolate(1.0f, 0.0f, maxTime);
+        lerpScale = new Interpolate(0.0f, (float) Math.PI, maxTime);
+
+        // Initialize connections between endpoints of the paths inside the piece
+        for (int i = 0; i < numConnections; i++)
+            connections[i] = -1;
+
+
+        // Randomize connections in the piece
+        Random rnd = new Random();
+        for (int i = 0; i < numConnections; i++)
+        {
+            while (connections[i] < 0)
+            {
+                int tmp = rnd.nextInt(numConnections);
+                if (tmp != i && connections[tmp] < 0)
+                {
+                    connections[i] = tmp;
+                    connections[tmp] = i;
+                }
+            }
+        }
+    }
+
+    public Piece()
+    {
+        this(PieceType.NONE);
+    }
+
+
+    public void changed()
+    {
+        typeChanged = true;
+    }
+
+    public void setType(PieceType ptype)
+    {
+//        typeChanged = (prevType != ptype) && (ptype == PieceType.LOCKED);
+        prevType = type;
+        type = ptype;
+    }
+
+    public void clearStates()
+    {
+        for (int i = 0; i < numConnections; i++)
+            states[i] = false;
+        stateChanged = true;
+    }
+
+    public int getRotation()
+    {
+        return currRotation % 4;
+    }
+
+    public int getRotatedPoint(int in)
+    {
+        int point = (in - (getRotation() * 2)) % 8;
+        if (point < 0) point = 8 + point;
+        return point;
+    }
+
+    public int getAntiRotatedPoint(int in)
+    {
+        int point = (in + (getRotation() * 2)) % 8;
+        if (point < 0) point = 8 + point;
+        return point;
+    }
+
+    public int getMatchingPoint(int point)
+    {
+        switch (point)
+        {
+            case 0: return 5;
+            case 1: return 4;
+
+            case 2: return 7;
+            case 3: return 6;
+
+            case 4: return 1;
+            case 5: return 0;
+
+            case 6: return 3;
+            case 7: return 2;
+        }
+        return -1;
+    }
+
+    public void setConnectionState(int point, boolean state)
+    {
+        states[point] = state;
+        states[connections[point]] = state;
+        stateChanged = true;
+    }
+
+    public int getConnection(int point)
+    {
+        return connections[point];
+    }
+
+    private float getAngle(float rotation)
+    {
+        return (float) ((rotation * Math.PI) / 2.0f);
+    }
+
+    public void rotate(RotateDir dir)
+    {
+        // Only normal
+        if (type != PieceType.LOCKED && type != PieceType.ACTIVE)
+            return;
+
+        currRotation = (currRotation + (dir == RotateDir.RIGHT ? 1 : -1));
+        lerpRotation = new Interpolate(getAngle(currRotation), currAngle, maxTime);
+        rotationChanged = true;
+    }
+
+    public Point2D getPointCoords(float x, float y, float dim, int index)
+    {
+        float ox = 0, oy = 0;
+        float step = dim / 10;
+
+        switch (index) {
+            // Normal line starting and ending points
+            case  0: ox = 3.0f; oy = 0.4f; break;
+            case  1: ox = 7.0f; oy = 0.4f; break;
+
+            case  2: ox = 9.6f; oy = 3.0f; break;
+            case  3: ox = 9.6f; oy = 7.0f; break;
+
+            case  4: ox = 7.0f; oy = 9.6f; break;
+            case  5: ox = 3.0f; oy = 9.6f; break;
+
+            case  6: ox = 0.4f; oy = 7.0f; break;
+            case  7: ox = 0.4f; oy = 3.0f; break;
+
+
+            // Matching control points for each point above (+8)
+            case  8: ox = 3.0f; oy = 2.5f; break;
+            case  9: ox = 7.0f; oy = 2.5f; break;
+
+            case 10: ox = 7.5f; oy = 3.0f; break;
+            case 11: ox = 7.5f; oy = 7.0f; break;
+
+            case 12: ox = 7.0f; oy = 7.5f; break;
+            case 13: ox = 3.0f; oy = 7.5f; break;
+
+            case 14: ox = 2.5f; oy = 7.0f; break;
+            case 15: ox = 2.5f; oy = 3.0f; break;
+        }
+
+        return new Point2D.Float(x + ox * step, y + oy * step);
+    }
+
+    public PieceType getType()
+    {
+        return type;
+    }
+
+    public void animate(float time)
+    {
+        active = false;
+
+        if (rotationChanged)
+        {
+            rotationTime = time;
+            rotationActive = true;
+            rotationChanged = false;
+            rotationSpeed = 1.0f;
+        }
+
+        if (typeChanged && type == PieceType.LOCKED)
+        {
+            rotationSpeed = 1.5f;
+        }
+
+        if (rotationActive)
+        {
+            float t = (time - rotationTime) * rotationSpeed;
+
+            if (t < maxTime)
+            {
+                currAngle = lerpRotation.getValue(t);
+            }
+            else
+            {
+                currAngle = lerpRotation.start;
+                rotationActive = false;
+            }
+
+            active = true;
+        }
+
+        if (typeChanged)
+        {
+            typeTime = time;
+            typeActive = true;
+            typeChanged = false;
+        }
+
+        if (typeActive)
+        {
+            float t = (time - typeTime) * 2.0f;
+
+            if (t < maxTime)
+            {
+                typeValue = lerpType.getValue(t);
+            }
+            else
+            {
+                typeValue = lerpType.start;
+                typeActive = false;
+            }
+
+
+            active = true;
+        }
+
+        if (stateChanged)
+        {
+        }
+
+        throbTime = (time % 100) / 100.0f;
+    }
+
+    public void paint(Graphics2D g, float x, float y, float dim)
+    {
+        AffineTransform save = g.getTransform();
+        Composite csave = g.getComposite();
+
+
+        // Change compositing alpha for the whole piece drawing
+        // when the piece is being "introduced".
+        if (typeActive)
+        {
+            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, typeValue));
+        }
+
+
+        // Transform drawing by current angle
+        g.rotate(currAngle, x + dim / 2.0f, y + dim / 2.0f);
+
+        // Color piece by type
+        switch (type) {
+            case LOCKED:  g.setPaint(new Color(0.3f, 0.8f, 0.3f, 0.35f)); break;
+            case ACTIVE:  g.setPaint(new Color(0.9f, 0.3f, 0.3f, 0.35f)); break;
+            case START:   g.setPaint(new Color(1.0f, 0.6f, 0.0f, 0.95f)); break;
+        }
+
+        float corner = dim / 10.0f;
+        g.fill(new RoundRectangle2D.Float(x, y, dim, dim, corner, corner));
+
+        // Start pieces (center piece) have a different kind of border
+        // and no connections drawn inside
+        if (type == PieceType.START)
+        {
+            // Draw piece border
+            g.setPaint(Color.black);
+            g.setStroke(new BasicStroke(2.0f));
+            g.draw(new RoundRectangle2D.Float(x, y, dim, dim, corner, corner));
+        }
+        else
+        {
+            // Active piece has a throbbing "ghost" border
+            if (type == PieceType.ACTIVE)
+            {
+                float offs1 = throbTime * 10.0f,
+                      offs2 = throbTime * 20.0f;
+
+                g.setPaint(new Color(0.0f, 0.0f, 0.0f, (float) (1.0f - throbTime) ));
+                g.setStroke(new BasicStroke(2.0f + throbTime * 2.0f));
+                g.draw(new RoundRectangle2D.Float(
+                    x - offs1, y - offs1,
+                    dim + offs2, dim + offs2,
+                    corner, corner));
+            }
+
+            // Draw piece border
+            g.setPaint(new Color(0.0f, 0.0f, 0.0f, 0.6f));
+            g.setStroke(new BasicStroke(5.0f));
+            g.draw(new RoundRectangle2D.Float(x, y, dim, dim, dim / 10, dim / 10));
+
+            // Draw the connections
+            g.setStroke(new BasicStroke(5.5f));
+            CubicCurve2D curve = new CubicCurve2D.Float();
+            boolean[] drawn = new boolean[numConnections];
+            for (int i = 0; i < numConnections; i++)
+            if (!drawn[i])
+            {
+                Point2D start, cp1, cp2, end;
+                boolean isActive = states[i] || states[connections[i]];
+
+                g.setPaint(isActive ? Color.white : Color.black);
+
+                start = getPointCoords(x, y, dim, i);
+                end   = getPointCoords(x, y, dim, connections[i]);
+                cp1   = getPointCoords(x, y, dim, i + 8);
+                cp2   = getPointCoords(x, y, dim, connections[i] + 8);
+
+                curve.setCurve(start, cp1, cp2, end);
+                g.draw(curve);
+
+                // Mark connection drawn, so we don't overdraw
+                drawn[i] = true;
+                drawn[connections[i]] = true;
+            }
+
+        } // !PieceType.START
+
+        g.setTransform(save);
+        g.setComposite(csave);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/PieceType.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,8 @@
+/*
+ * Ristipolku
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+public enum PieceType { START, LOCKED, ACTIVE, NONE }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/ResourceLoader.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,37 @@
+/*
+ * Ristipolku Resource Loader
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.util.*;
+import java.io.*;
+import java.net.*;
+
+
+public class ResourceLoader
+{
+    InputStream stream;
+    String name;
+    URL resourceURL;
+
+    public ResourceLoader(String name)
+    {
+        this.name = name;
+        resourceURL = getClass().getClassLoader().getResource(name);
+        if (resourceURL != null)
+            stream = getClass().getClassLoader().getResourceAsStream(name);
+
+        System.out.print("ResourceLoader('"+ name +"'): "+ resourceURL +" - "+ stream +"\n");
+    }
+
+    public InputStream getStream()
+    {
+        return stream;
+    }
+
+    public URL getURL()
+    {
+        return resourceURL;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/Sound.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,17 @@
+package game;
+
+public class Sound {
+
+    private byte[] samples;
+
+    public Sound(byte[] samples)
+    {
+        this.samples = samples;
+    }
+
+    public byte[] getSamples()
+    {
+        return samples;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/game/SoundManager.java	Thu Dec 01 14:33:25 2016 +0200
@@ -0,0 +1,459 @@
+/*
+ * Ristipolku Game Engine
+ * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ */
+package game;
+
+import java.util.*;
+import java.io.*;
+import game.*;
+import javax.sound.sampled.*;
+
+
+public class SoundManager extends ThreadGroup
+{
+    private boolean alive;
+    private LinkedList queue;
+    private int id;
+    private static int poolID;
+
+    private AudioFormat playbackFormat;
+    private ThreadLocal localLine;
+    private ThreadLocal localBuffer;
+    private Object pausedLock;
+    private boolean paused;
+
+
+    public SoundManager(AudioFormat format)
+    {
+        this(format, getMaxSimultaneousSounds(format));
+    }
+
+
+    public SoundManager(AudioFormat format, int maxSounds)
+    {
+        super("SoundManagerPool-" + (poolID++));
+
+        int numThreads = Math.min(maxSounds, getMaxSimultaneousSounds(playbackFormat));
+
+        System.out.print("SMGR.SoundManager() initializing with " + numThreads +" max sounds\n");
+
+        setDaemon(true);
+        alive = true;
+
+        playbackFormat = format;
+        localLine = new ThreadLocal();
+        localBuffer = new ThreadLocal();
+        pausedLock = new Object();
+
+        queue = new LinkedList();
+        for (int i = 0; i < numThreads; i++)
+            new PooledThread().start();
+
+        synchronized (this)
+        {
+            notifyAll();
+        }
+    }
+
+
+    public static int getMaxSimultaneousSounds(AudioFormat playbackFormat)
+    {
+        DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
+
+        Mixer.Info[] info = AudioSystem.getMixerInfo();
+        System.out.print("SMGR.getMaxSimultaneousSounds() mixer information:\n");
+        Mixer.Info select = null;
+        for (Mixer.Info i : info)
+        {
+            System.out.print("  - '"+i.getName()+"'\n");
+            if (i.getName().equals("Java Sound Audio Engine"))
+                select = i;
+        }
+
+        Mixer mixer = AudioSystem.getMixer(select);
+
+        Mixer.Info i = mixer.getMixerInfo();
+        System.out.print("  * selected='"+i.getName()+"'\n");
+
+        int maxLines = mixer.getMaxLines(lineInfo);
+        if (maxLines == AudioSystem.NOT_SPECIFIED)
+            maxLines = 8;
+
+        System.out.print("  * maxLines="+maxLines+"\n");
+
+        return maxLines;
+    }
+
+
+    protected void cleanUp()
+    {
+        System.out.print("SMGR.cleanUp()\n");
+        // signal to unpause
+        setPaused(false);
+
+        System.out.print("SMGR.cleanUp(): closing mixer\n");
+
+        // close the mixer (stops any running sounds)
+        Mixer mixer = AudioSystem.getMixer(null);
+        if (mixer.isOpen())
+            mixer.close();
+
+        System.out.print("SMGR.cleanUp(): leaving\n");
+    }
+
+
+    public void setPaused(boolean paused)
+    {
+        if (this.paused != paused)
+        {
+            synchronized (pausedLock)
+            {
+                this.paused = paused;
+                if (!paused)
+                {
+                    // restart sounds
+                    pausedLock.notifyAll();
+                }
+            }
+        }
+    }
+
+
+    public boolean isPaused()
+    {
+        return paused;
+    }
+
+
+    public Sound getSound(String filename)
+    {
+        return getSound(getAudioInputStream(filename));
+    }
+
+
+    public Sound getSound(InputStream is)
+    {
+        return getSound(getAudioInputStream(is));
+    }
+
+
+    public Sound getSound(AudioInputStream audioStream)
+    {
+        if (audioStream == null)
+            return null;
+
+        // get the number of bytes to read
+        int length = (int)(audioStream.getFrameLength() * audioStream.getFormat().getFrameSize());
+
+        // read the entire stream
+        byte[] samples = new byte[length];
+        DataInputStream is = new DataInputStream(audioStream);
+        try {
+            is.readFully(samples);
+            is.close();
+        }
+        catch (IOException ex)
+        {
+            ex.printStackTrace();
+        }
+
+        // return the samples
+        return new Sound(samples);
+    }
+
+
+    public AudioInputStream getAudioInputStream(String filename)
+    {
+        ResourceLoader res = new ResourceLoader(filename);
+        if (res == null || res.getStream() == null)
+        {
+            System.out.print("Could not load audio resource '"+ filename +"'.\n");
+            return null;
+        }
+        try {
+            return getAudioInputStream(res.getStream());
+        }
+        catch (Exception ex)
+        {
+            System.out.print("Could not get AudioInputStream for '"+ filename +"'\n");
+            return null;
+        }
+    }
+
+
+    public AudioInputStream getAudioInputStream(InputStream is)
+    {
+        try {
+            if (!is.markSupported())
+                is = new BufferedInputStream(is);
+
+            // open the source stream
+            AudioInputStream source = AudioSystem.getAudioInputStream(is);
+
+            // convert to playback format
+            return AudioSystem.getAudioInputStream(playbackFormat, source);
+        }
+
+        catch (UnsupportedAudioFileException ex) {
+            ex.printStackTrace();
+        }
+        catch (IOException ex) {
+            ex.printStackTrace();
+        }
+        catch (IllegalArgumentException ex) {
+            ex.printStackTrace();
+        }
+
+        return null;
+    }
+
+
+    public InputStream play(Sound sound)
+    {
+        InputStream is;
+        if (sound != null)
+        {
+            is = new ByteArrayInputStream(sound.getSamples());
+            return play(is);
+        }
+        return null;
+    }
+
+
+    public InputStream play(InputStream is)
+    {
+        System.out.print("SMGR.play(is="+is+")\n");
+        if (is != null)
+        {
+            runTask(new SoundPlayer(is));
+        }
+        return is;
+    }
+
+
+    protected void threadStarted()
+    {
+        synchronized (this)
+        {
+            try {
+                wait();
+            }
+            catch (InterruptedException ex) { }
+        }
+
+
+        // use a short, 100ms (1/10th sec) buffer for filters that
+        // change in real-time
+        int bufferSize = playbackFormat.getFrameSize() * Math.round(playbackFormat.getSampleRate() / 10);
+
+        // create, open, and start the line
+        SourceDataLine line;
+        DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
+
+        System.out.print("SMGR.threadStarted(): "+lineInfo.toString()+"\n");
+
+        try {
+            line = (SourceDataLine) AudioSystem.getLine(lineInfo);
+            line.open(playbackFormat, bufferSize);
+        }
+        catch (LineUnavailableException ex)
+        {
+            System.out.print("SMGR.threadStarted() line unavailable!\n");
+            // the line is unavailable - signal to end this thread
+            Thread.currentThread().interrupt();
+            return;
+        }
+
+        // Change volume
+        Control.Type ct = FloatControl.Type.MASTER_GAIN;
+        if (line.isControlSupported(ct))
+        {
+            FloatControl c = (FloatControl) line.getControl(ct);
+            c.setValue(-20f);
+        }
+
+        line.start();
+
+        // create the buffer
+        byte[] buffer = new byte[bufferSize];
+
+        // set this thread's locals
+        localLine.set(line);
+        localBuffer.set(buffer);
+    }
+
+
+    protected void threadStopped()
+    {
+        System.out.print("SMGR.threadStopped()\n");
+        SourceDataLine line = (SourceDataLine) localLine.get();
+        if (line != null)
+        {
+            line.drain();
+            line.close();
+        }
+    }
+
+
+    protected class SoundPlayer implements Runnable
+    {
+        private InputStream source;
+
+        public SoundPlayer(InputStream source)
+        {
+            this.source = source;
+        }
+
+        public void run()
+        {
+            // get line and buffer from ThreadLocals
+            SourceDataLine line = (SourceDataLine) localLine.get();
+            byte[] buffer = (byte[])localBuffer.get();
+
+            if (line == null || buffer == null)
+                return;
+
+            // copy data to the line
+            try {
+                boolean playing = true;
+                while (playing) {
+                    // if paused, wait until unpaused
+                    synchronized (pausedLock)
+                    {
+                        if (paused) {
+                            try {
+                                pausedLock.wait();
+                            }
+                            catch (InterruptedException ex) {
+                                return;
+                            }
+                        }
+                    }
+
+                    // copy data
+                    int bufPos = 0;
+                    while (bufPos < buffer.length && playing)
+                    {
+                        int res = source.read(buffer, bufPos, buffer.length - bufPos);
+                        if (res != -1)
+                            bufPos += res;
+                        else
+                            playing = false;
+                    }
+                    if (playing)
+                        line.write(buffer, 0, bufPos);
+                }
+            }
+
+            catch (IOException ex) {
+                ex.printStackTrace();
+            }
+
+        }
+    }
+
+    public synchronized void runTask(Runnable task)
+    {
+        if (!alive)
+        {
+            throw new IllegalStateException();
+        }
+        if (task != null)
+        {
+            queue.add(task);
+            notify();
+        }
+
+    }
+
+    protected synchronized Runnable getTask() throws InterruptedException
+    {
+        while (queue.size() == 0)
+        {
+            if (!alive)
+                return null;
+
+            wait();
+        }
+        return (Runnable) queue.removeFirst();
+    }
+
+
+    public synchronized void close()
+    {
+        System.out.print("SMGR.close()\n");
+
+        if (alive)
+        {
+            System.out.print("SMGR.close(): alive queue clear\n");
+            // Clear queue
+            alive = false;
+            queue.clear();
+            interrupt();
+        }
+
+        cleanUp();
+        System.out.print("SMGR.close(): leaving\n");
+    }
+
+
+    public void join()
+    {
+        System.out.print("SMGR.join()\n");
+        cleanUp();
+
+        synchronized (this)
+        {
+            alive = false;
+            notifyAll();
+        }
+
+        Thread[] threads = new Thread[activeCount()];
+        int count = enumerate(threads);
+        for (int i = 0; i < count; i++)
+        {
+            try {
+                threads[i].join();
+            }
+            catch (InterruptedException ex)
+            {
+            }
+        }
+    }
+
+
+    private class PooledThread extends Thread
+    {
+        public PooledThread()
+        {
+            super(SoundManager.this, "SoundManagerPool-" + (id++));
+        }
+
+        public void run()
+        {
+            threadStarted();
+
+            while (!isInterrupted()) {
+                Runnable task = null;
+                try {
+                    task = getTask();
+                }
+                catch (InterruptedException ex)
+                {
+                }
+
+                if (task == null)
+                    break;
+
+                try {
+                    task.run();
+                }
+                catch (Throwable t) {
+                    uncaughtException(this, t);
+                }
+            }
+            threadStopped();
+        }
+    }
+}
--- a/src/AnimatedPointElement.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/*
- * Ristipolku animated game element
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.awt.*;
-import java.awt.geom.*;
-import java.util.*;
-import java.math.*;
-
-
-public class AnimatedPointElement
-{
-    private float stime, value, steps;
-    private IDMPoint pos;
-    private Interpolate lerpV, lerpX, lerpY;
-    boolean active, first;
-    String text;
-
-    public AnimatedPointElement(IDMPoint start, String text)
-    {
-        Random rnd = new Random();
-        steps = 100;
-
-        lerpX = new Interpolate(start.x - rnd.nextInt(100) + 50, start.x, steps);
-        lerpY = new Interpolate(start.y - rnd.nextInt(100) + 50, start.y, steps);
-        lerpV = new Interpolate(0.0f, 1.0f, steps);
-
-        pos = new IDMPoint(start);
-        active = false;
-        first = true;
-        this.text = text;
-    }
-
-    public void animate(float time)
-    {
-        if (first)
-        {
-            first = false;
-            active = true;
-            stime = time;
-        }
-
-        if (active)
-        {
-            float t = (time - stime);
-            if (t >= steps)
-            {
-                t = steps;
-                active = false;
-            }
-
-            value = lerpV.getValue(t);
-            pos.x = lerpX.getValue(t);
-            pos.y = lerpY.getValue(t);
-        }
-    }
-
-    public void paint(Graphics2D g)
-    {
-        if (!active)
-            return;
-
-        Paint psave = g.getPaint();
-//        AffineTransform tsave = g.getTransform();
-
-        g.setFont(G.fonts[2]);
-        g.setPaint(new Color(value, value, value, value));
-        g.drawString(text, pos.x, pos.y);
-
-//        g.setTransform(tsave);
-        g.setPaint(psave);
-    }
-}
--- a/src/Engine.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,909 +0,0 @@
-/*
- * Ristipolku Game Engine
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.awt.*;
-import java.awt.geom.*;
-import java.awt.event.*;
-import java.awt.image.*;
-import java.awt.event.*;
-import java.awt.font.*;
-import javax.imageio.*;
-import javax.swing.*;
-import java.util.*;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.io.*;
-import game.*;
-import javax.sound.sampled.*;
-
-
-class AboutBox extends IDMWidget
-{
-    BufferedImage aboutImg;
-    boolean aboutSecret;
-
-    IDMPoint currPos, currOffs;
-    Paint textPaint;
-    Font textFont;
-    FontMetrics textMetrics;
-
-
-    public AboutBox()
-    {
-        try {
-            ResourceLoader res = new ResourceLoader("graphics/girl.jpg");
-            aboutImg = ImageIO.read(res.getStream());
-        }
-        catch (IOException e)
-        {
-        }
-
-        aboutSecret = false;
-
-        setPos(150f, 150f);
-        setSize(675f, 420f);
-    }
-
-    public void setTextFont(Font font, FontMetrics metrics)
-    {
-        textFont = font;
-        textMetrics = metrics;
-    }
-
-    public void setTextPaint(Paint paint)
-    {
-        textPaint = paint;
-    }
-
-    public void setCurrPos(IDMPoint npos)
-    {
-        currPos = npos;
-        currOffs = new IDMPoint(0, 0);
-    }
-
-    public void setCurrPos(float x, float y)
-    {
-        setCurrPos(new IDMPoint(x, y));
-    }
-
-    public void drawString(Graphics2D g, String text)
-    {
-        Paint savePaint = g.getPaint();
-        g.setPaint(textPaint);
-        g.setFont(textFont);
-
-        int i = 0;
-        while (i < text.length())
-        {
-            int p = text.indexOf("\n", i);
-            boolean linefeed;
-            String str;
-            if (p >= i)
-            {
-                str = text.substring(i, p);
-                i = p + 1;
-                linefeed = true;
-            }
-            else
-            {
-                str = text.substring(i);
-                i += str.length();
-                linefeed = false;
-            }
-
-            g.drawString(str, currPos.x + currOffs.x, currPos.y + currOffs.y);
-
-            if (linefeed)
-            {
-                currOffs.x = 0;
-                currOffs.y += textMetrics.getHeight();
-            }
-            else
-            {
-                currOffs.x += textMetrics.stringWidth(str);
-            }
-        }
-
-        g.setPaint(savePaint);
-    }
-
-    public void paint(Graphics2D g)
-    {
-        int x = getScaledX(), y = getScaledY(),
-            w = getScaledWidth(), h = getScaledHeight();
-
-        g.setPaint(new Color(0.0f, 0.0f, 0.0f, 0.7f));
-        g.fill(new RoundRectangle2D.Float(x, y, w, h, 10, 10));
-
-        setTextFont(G.fonts[3], G.metrics[3]);
-        setTextPaint(Color.white);
-
-        setCurrPos(x + 20, y + 30);
-        drawString(g, "RistiPolku (CrossPaths) v"+ G.version +"\n");
-
-        setTextFont(G.fonts[1], G.metrics[1]);
-        if (aboutSecret)
-        {
-            g.drawImage(aboutImg, x + 20, y + 55,
-                aboutImg.getWidth(), aboutImg.getHeight(), null);
-
-            setCurrPos(x + 225, y + 75);
-            drawString(g,
-                "Dedicated to my\n" +
-                "favorite woman\n" +
-                "in the world.");
-
-            setCurrPos(x + 370, y + 175);
-            drawString(g, "- Matti");
-        }
-        else
-        {
-            setTextPaint(Color.yellow);
-            drawString(g, "(c) Copyright 2011 Tecnic Software productions (TNSP)\n");
-
-            setTextPaint(Color.white);
-            drawString(g, "Programming, graphics and design by " +
-                          "Matti 'ccr' Hämäläinen.\n" +
-                          "Audio from archive.org, used non-commercially.\n \n");
-
-            setTextPaint(Color.red);
-            drawString(g, "Controls:\n");
-
-            IDMPoint old = currOffs.copy();
-
-            setTextPaint(Color.white);
-            drawString(g,
-            "Arrow keys / mouse wheel\n"+
-            "Enter / mouse click\n"+
-            "Space bar\n");
-
-            currPos.x += 330;
-            currOffs.y = old.y;
-            drawString(g,
-            "- Rotate piece\n" +
-            "- Place/lock piece\n" +
-            "- Swap piece\n");
-
-            currPos.x -= 330;
-            setTextPaint(Color.green);
-            drawString(g,
-            "\nObjective: Create a path long as possible by rotating\n"+
-            "and placing pieces. More points will be awarded for\n"+
-            "advancing the path by several segments per turn."
-            );
-        }
-    }
-
-    public boolean keyPressed(KeyEvent e)
-    {
-        if (e.getKeyCode() == KeyEvent.VK_L)
-        {
-            aboutSecret = true;
-        }
-        else
-        {
-            clicked();
-            aboutSecret = false;
-        }
-        return true;
-    }
-
-    public void clicked()
-    {
-        parent.remove(this);
-    }
-}
-
-
-class GameBoard extends IDMWidget
-{
-    static final int boardSize = 9;
-    static final int boardMiddle = 4;
-    Piece[][] board;
-    float pscale, ptime;
-
-    public boolean flagGameOver, flagBoardIsDirty;
-    int gameScore;
-
-    Piece currPiece, nextPiece;
-    int currX, currY, currPoint;
-
-    Sound sndPlaced;
-
-    private final ReentrantReadWriteLock pointLock = new ReentrantReadWriteLock();
-    private ArrayList<AnimatedPointElement> pointElems;
-
-    public GameBoard(IDMPoint pos, float ps)
-    {
-        super(pos);
-        pscale = ps;
-
-//        sndPlaced = G.smgr.getSound("sounds/placed.wav");
-
-        pointElems = new ArrayList<AnimatedPointElement>();
-
-        startNewGame();
-    }
-
-    public void startNewGame()
-    {
-        board = new Piece[boardSize][boardSize];
-        board[boardMiddle][boardMiddle] = new Piece(PieceType.START);
-
-        currX = boardMiddle;
-        currY = boardMiddle;
-        currPoint = 0;
-
-        currPiece = null;
-        nextPiece = new Piece(PieceType.ACTIVE);
-
-        flagGameOver = false;
-        flagBoardIsDirty = true;
-        pieceFinishTurn();
-        gameScore = 0;
-    }
-
-    public void paintBackPlate(Graphics2D g)
-    {
-        g.setPaint(new Color(0.0f, 0.0f, 0.0f, 0.2f));
-        g.setStroke(new BasicStroke(5.0f));
-        g.draw(new RoundRectangle2D.Float(getScaledX(), getScaledY(),
-            boardSize * pscale, boardSize * pscale,
-            pscale / 5, pscale / 5));
-    }
-
-    public void paintBoard(Graphics2D g, boolean drawCurrent)
-    {
-        for (int y = 0; y < boardSize; y++)
-        for (int x = 0; x < boardSize; x++)
-        if (board[x][y] != null)
-        {
-            if ((drawCurrent && board[x][y] == currPiece) ||
-                (!drawCurrent && board[x][y] != currPiece))
-            {
-                board[x][y].paint(g,
-                    getScaledX() + (x * pscale),
-                    getScaledY() + (y * pscale),
-                    pscale - pscale / 10);
-            }
-        }
-    }
-
-    public void paint(Graphics2D g)
-    {
-        paintBoard(g, true);
-
-        Lock read = pointLock.readLock();
-        read.lock();
-        try
-        {
-            for (AnimatedPointElement elem : pointElems)
-            {
-                elem.paint(g);
-            }
-        }
-        finally
-        {
-            read.unlock();
-        }
-
-
-        if (!flagGameOver)
-        {
-            if (nextPiece != null)
-            {
-                // Draw next piece
-                AffineTransform save = g.getTransform();
-                nextPiece.paint(g, G.screenDim.width * 0.85f - 90.0f/2.0f, G.screenDim.height * 0.43f, 90.0f);
-                g.setTransform(save);
-            }
-        }
-        else
-        {
-            // Game over text
-            String text = "Game Over!";
-            int textWidth = G.metrics[2].stringWidth(text);
-            g.setFont(G.fonts[2]);
-
-            g.setPaint(new Color(0.0f, 0.0f, 0.0f, 0.5f));
-            g.drawString(text, (G.screenDim.width - textWidth) / 2 + 5, G.screenDim.height / 2 + 5);
-
-            double f = Math.sin(ptime * 0.1) * 4.0;
-            g.setPaint(Color.white);
-            g.drawString(text, (G.screenDim.width - textWidth) / 2 + (float) f, G.screenDim.height / 2 + (float) f);
-        }
-
-        // Score
-        String text = ""+ String.format("%05d", gameScore);
-        int textWidth = G.metrics[2].stringWidth(text);
-        g.setFont(G.fonts[2]);
-        g.setPaint(Color.white);
-        g.drawString(text, (G.screenDim.width * 0.85f) - textWidth / 2, G.screenDim.height * 0.3f);
-    }
-
-    public boolean contains(float x, float y)
-    {
-        return (x >= getScaledX() &&
-                y >= getScaledY() &&
-                x < getScaledX() + boardSize * pscale &&
-                y < getScaledY() + boardSize * pscale);
-    }
-
-    public boolean isBoardDirty()
-    {
-        if (flagBoardIsDirty)
-        {
-            flagBoardIsDirty = false;
-            return true;
-        }
-        else
-            return false;
-    }
-
-    public void animate(float time)
-    {
-        if (nextPiece != null)
-        {
-            nextPiece.animate(time);
-        }
-            
-        ptime = time;
-        for (int y = 0; y < boardSize; y++)
-        for (int x = 0; x < boardSize; x++)
-        if (board[x][y] != null)
-        {
-            board[x][y].animate(time);
-            if (board[x][y] != currPiece && board[x][y].active)
-                flagBoardIsDirty = true;
-        }
-
-        Lock write = pointLock.writeLock();
-        write.lock();
-        try
-        {
-            ArrayList<AnimatedPointElement> tmp = new ArrayList<AnimatedPointElement>();
-
-            for (AnimatedPointElement elem : pointElems)
-            {
-                elem.animate(time);
-                if (elem.active)
-                    tmp.add(elem);
-            }
-
-            pointElems = tmp;
-
-            if (time % 32 == 1)
-            {
-                Random rnd = new Random();
-                pointElems.add(new AnimatedPointElement(
-                    new IDMPoint(10 + rnd.nextInt(400), 10 + rnd.nextInt(100)), "."));
-            }
-        }
-        finally
-        {
-            write.unlock();
-        }
-    }
-
-    public void pieceRotate(Piece.RotateDir dir)
-    {
-        if (currPiece != null && !flagGameOver)
-        {
-            currPiece.rotate(dir);
-        }
-    }
-
-    // Change coordinates based on the "outgoing"
-    // piece connection point.
-    private void pieceMoveTo(int point)
-    {
-        switch (point)
-        {
-            case 0: currY--; break;
-            case 1: currY--; break;
-
-            case 2: currX++; break;
-            case 3: currX++; break;
-
-            case 4: currY++; break;
-            case 5: currY++; break;
-
-            case 6: currX--; break;
-            case 7: currX--; break;
-        }
-    }
-
-    public void pieceCreateNew()
-    {
-        currPiece = nextPiece;
-        currPiece.changed();
-        nextPiece = new Piece(PieceType.ACTIVE);
-        flagBoardIsDirty = true;
-    }
-
-    public void pieceSwapCurrent()
-    {
-        if (!flagGameOver)
-        {
-            Piece tmp = currPiece;
-            currPiece = nextPiece;
-            nextPiece = tmp;
-            board[currX][currY] = currPiece;
-            currPiece.changed();
-            nextPiece.changed();
-            flagBoardIsDirty = true;
-        }
-    }
-
-    // Check one piece, set connections, find the new placement
-    // based on piece rotations etc.
-    private boolean pieceCheck(Piece piece)
-    {
-        if (piece == null)
-        {
-            // Create new piece
-            pieceCreateNew();
-            board[currX][currY] = currPiece;
-            return true;
-        }
-        else
-        if (piece.getType() == PieceType.START)
-        {
-            if (currPiece != null)
-            {
-                // Hit center starting piece, game over
-                flagGameOver = true;
-                return true;
-            }
-            else
-            {
-                // Start piece as first piece means game is starting
-                pieceMoveTo(currPoint);
-                pieceCreateNew();
-                board[currX][currY] = currPiece;
-                return true;
-            }
-        }
-
-        // Mark the current piece as locked
-        piece.setType(PieceType.LOCKED);
-
-        // Solve connection (with rotations) through the piece
-        currPoint = piece.getRotatedPoint(piece.getMatchingPoint(currPoint));
-
-        // Mark connection as active
-        piece.setConnectionState(currPoint, true);
-
-        // Solve exit point (with rotations)
-        currPoint = piece.getAntiRotatedPoint(piece.getConnection(currPoint));
-
-        // Move to next position accordingly
-        pieceMoveTo(currPoint);
-        return false;
-    }
-
-    // Finish one move/turn of the game, resolve path and find placement
-    // of the next piece, or set "game over" state if required.
-    public void pieceFinishTurn()
-    {
-        boolean finished = false;
-        int connections = 0;
-
-        if (currPiece != null)
-        {
-            G.smgr.play(sndPlaced);
-        }
-
-        while (!finished)
-        {
-            if (currX >= 0 && currX < boardSize && currY >= 0 && currY < boardSize)
-            {
-                int oldX = currX, oldY = currY;
-                connections++;
-                finished = pieceCheck(board[currX][currY]);
-
-                if (!finished)
-                {
-                    Lock write = pointLock.writeLock();
-                    write.lock();
-                    try
-                    {
-                        pointElems.add(new AnimatedPointElement(
-                            new IDMPoint(
-                            getScaledX() + ((oldX + 0.5f) * pscale),
-                            getScaledY() + ((oldY + 0.5f) * pscale)),
-                            "" + connections));
-                    }
-                    finally
-                    {
-                        write.unlock();
-                    }
-                }
-
-            }
-            else
-            {
-                // Outside of the board, game over
-                finished = true;
-                flagGameOver = true;
-            }
-        }
-
-        // Compute and add score
-        gameScore += connections * connections;
-
-        // If game over, clear the game
-        if (flagGameOver)
-        {
-            currPiece = null;
-        }
-
-        flagBoardIsDirty = true;
-    }
-
-    public boolean mouseWheelMoved(MouseWheelEvent e)
-    {
-        int notches = e.getWheelRotation();
-
-        if (notches < 0)
-            pieceRotate(Piece.RotateDir.LEFT);
-        else
-            pieceRotate(Piece.RotateDir.RIGHT);
-
-        return true;
-    }
-
-    public void clicked()
-    {
-        if (!flagGameOver)
-            pieceFinishTurn();
-    }
-
-    public boolean keyPressed(KeyEvent e)
-    {
-        if (flagGameOver)
-            return false;
-
-        switch (e.getKeyCode())
-        {
-            case KeyEvent.VK_LEFT:
-            case KeyEvent.VK_UP:
-                pieceRotate(Piece.RotateDir.LEFT);
-                return true;
-
-            case KeyEvent.VK_RIGHT:
-            case KeyEvent.VK_DOWN:
-                pieceRotate(Piece.RotateDir.RIGHT);
-                return true;
-
-            case KeyEvent.VK_ENTER:
-                pieceFinishTurn();
-                return true;
-        }
-        return false;
-    }
-}
-
-
-public class Engine extends JPanel
-                    implements Runnable, KeyListener,
-                    MouseListener, MouseWheelListener
-{
-    long startTime;
-    float gameUpdates, gameFrames;
-
-    Thread animThread;
-    boolean animEnable = false;
-
-    GameBoard lauta = null;
-    InputStream musa;
-    IDMContainer widgets;
-    AboutBox aboutBox;
-
-    public void dbg(String msg)
-    {
-        System.out.print("Engine: " + msg);
-    }
-
-    public Engine()
-    {
-        // Initialize globals
-        System.out.print("Engine() constructor\n");
-
-        // Sound system
-        G.smgr = new SoundManager(new AudioFormat(22050, 16, 1, true, false), 1);
-
-        // Load resources
-        try
-        {
-            ResourceLoader res = new ResourceLoader("graphics/board.jpg");
-            G.lautaBG = ImageIO.read(res.getStream());
-
-            try {
-                res = new ResourceLoader("graphics/font.ttf");
-
-                G.fonts = new Font[G.numFonts];
-                G.fonts[0] = Font.createFont(Font.TRUETYPE_FONT, res.getStream());
-                G.fonts[1] = G.fonts[0].deriveFont(24f);
-                G.fonts[2] = G.fonts[0].deriveFont(64f);
-                G.fonts[3] = G.fonts[0].deriveFont(32f);
-            }
-            catch (FontFormatException e)
-            {
-                dbg("Could not initialize fonts.\n");
-            }
-
-            res = new ResourceLoader("sounds/gamemusic.wav");
-            musa = res.getStream();
-        }
-        catch (IOException e)
-        {
-            JOptionPane.showMessageDialog(null,
-                e.getMessage(),
-                "Initialization error",
-                JOptionPane.ERROR_MESSAGE);
-
-            dbg(e.getMessage());
-        }
-
-        // Create IDM GUI widgets
-        widgets = new IDMContainer();
-
-        lauta = new GameBoard(new IDMPoint(95, 130), 63);
-        widgets.add(lauta);
-
-        widgets.add(new BtnSwapPiece(767f, 450f));
-        widgets.add(new BtnAbout    (767f, 550f));
-        widgets.add(new BtnNewGame  (767f, 630f));
-
-        aboutBox = new AboutBox();
-
-        // Game
-        startNewGame();
-
-        // Initialize event listeners
-        addKeyListener(this);
-        addMouseListener(this);
-        addMouseWheelListener(this);
-
-        // Start playing background music
-        G.smgr.play(musa);
-
-        // Get initial focus
-        if (!hasFocus())
-        {
-            dbg("Requesting focus.\n");
-            requestFocus();
-        }
-
-        gameUpdates = 0;
-    }
-
-    public void startNewGame()
-    {
-        gameFrames = 0;
-        startTime = new Date().getTime();
-        lauta.startNewGame();
-    }
-
-    public void paintComponent(Graphics g)
-    {
-        Graphics2D g2 = (Graphics2D) g;
-        boolean scaleChanged = false,
-                updateBoard = lauta.isBoardDirty();
-
-        // Rescale if parent component size has changed
-        Dimension dim = getSize();
-        if (G.screenDim == null || !dim.equals(G.screenDim))
-        {
-            float dw = dim.width / 1024.0f,
-                  dh = dim.height / 768.0f;
-
-            // Rescale IDM GUI widgets
-            widgets.setScale(dw, dh);
-            G.screenDim = dim;
-
-            // Rescale background image
-            // Rescale fonts
-            G.fonts[1] = G.fonts[0].deriveFont(24f * dw);
-            G.fonts[2] = G.fonts[0].deriveFont(64f * dw);
-            G.fonts[3] = G.fonts[0].deriveFont(32f * dw);
-
-            dbg("Scale changed.\n");
-            scaleChanged = true;
-            updateBoard = true;
-        }
-        
-        if (updateBoard)
-        {
-//            dbg("updateBoard()\n");
-            G.lautaBGScaled = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_RGB);
-            Graphics2D gimg = G.lautaBGScaled.createGraphics();
-            gimg.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
-                                  RenderingHints.VALUE_INTERPOLATION_BICUBIC);
-
-            gimg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-                                RenderingHints.VALUE_ANTIALIAS_ON);
-
-            gimg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
-                                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-
-            gimg.drawImage(G.lautaBG, 0, 0, dim.width, dim.height, null);
-            lauta.paintBackPlate(gimg);
-            lauta.paintBoard(gimg, false);
-        }
-
-        // Get font metrics against current Graphics2D context
-        if (G.metrics == null || scaleChanged)
-        {
-            G.metrics = new FontMetrics[G.numFonts];
-            for (int i = 0; i < G.numFonts; i++)
-                G.metrics[i] = g2.getFontMetrics(G.fonts[i]);
-        }
-
-        // Draw background image, pieces, widgets
-        g2.drawImage(G.lautaBGScaled, 0, 0, null);
-
-        // Use antialiasing when rendering the game elements
-        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-                            RenderingHints.VALUE_ANTIALIAS_ON);
-
-        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
-                            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-
-        widgets.paint(g2);
-
-
-        // Frames per second counter
-/*
-        g2.setFont(G.fonts[1]);
-        long currTime = new Date().getTime();
-        g2.drawString("fps = "+ ((gameFrames * 1000) / (currTime - startTime)), G.screenDim.width - 120, 20);
-*/
-        gameFrames++;
-    }
-
-    public void startThreads()
-    {
-        dbg("startThreads()\n");
-        if (animThread == null)
-        {
-            animThread = new Thread(this);
-            animEnable = true;
-            animThread.start();
-        }
-    }
-
-    public void stopThreads()
-    {
-        dbg("stopThreads()\n");
-
-        // Stop animations
-        if (animThread != null)
-        {
-            animThread.interrupt();
-            animEnable = false;
-            animThread = null;
-        }
-
-        // Shut down sound manager
-        G.smgr.close();
-    }
-
-    public void mouseEntered(MouseEvent e)
-    {
-        widgets.mouseEntered(e);
-    }
-    public void mouseExited(MouseEvent e)
-    {
-        widgets.mouseExited(e);
-    }
-
-    public void mousePressed(MouseEvent e)
-    {
-        if (widgets.containsObject(aboutBox))
-            aboutBox.mousePressed(e);
-        else
-            widgets.mousePressed(e);
-    }
-
-    public void mouseReleased(MouseEvent e)
-    {
-        if (widgets.containsObject(aboutBox))
-            aboutBox.mouseReleased(e);
-        else
-            widgets.mouseReleased(e);
-    }
-
-    public void mouseClicked(MouseEvent e)
-    {
-        if (!hasFocus())
-        {
-            dbg("Requesting focus.\n");
-            requestFocus();
-        }
-    }
-
-    public void mouseWheelMoved(MouseWheelEvent e)
-    {
-        lauta.mouseWheelMoved(e);
-    }
-
-    public void keyTyped(KeyEvent e) { }
-    public void keyReleased(KeyEvent e) { }
-
-    public void keyPressed(KeyEvent e)
-    {
-        // Handle keyboard input
-        if (widgets.containsObject(aboutBox))
-            aboutBox.keyPressed(e);
-        else
-            widgets.keyPressed(e);
-    }
-
-    public void run()
-    {
-        while (animEnable)
-        {
-            // Progress game animation clock
-            gameUpdates++;
-
-            // Animate components
-            lauta.animate(gameUpdates);
-
-            // Repaint with a frame limiter
-            if (gameUpdates % 4 == 1)
-                repaint();
-
-            // Sleep for a moment
-            try {
-                Thread.sleep(10);
-            }
-            catch (InterruptedException x) {
-            }
-        }
-    }
-
-    class BtnNewGame extends IDMButton
-    {
-        public BtnNewGame(float x, float y)
-        {
-            super(x, y, KeyEvent.VK_ESCAPE, G.fonts[1], "New Game");
-        }
-
-        public void clicked()
-        {
-            startNewGame();
-        }
-    }
-
-    class BtnSwapPiece extends IDMButton
-    {
-        public BtnSwapPiece(float x, float y)
-        {
-            super(x, y, KeyEvent.VK_SPACE, G.fonts[1], "Swap");
-        }
-
-        public void clicked()
-        {
-            lauta.pieceSwapCurrent();
-        }
-    }
-
-    class BtnAbout extends IDMButton
-    {
-        public BtnAbout(float x, float y)
-        {
-            super(x, y, KeyEvent.VK_A, G.fonts[1], "About");
-        }
-
-        public void clicked()
-        {
-            if (!widgets.containsObject(aboutBox))
-                widgets.add(aboutBox);
-        }
-    }
-}
--- a/src/G.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-package game;
-
-import java.awt.*;
-import java.awt.geom.*;
-import java.awt.event.*;
-import java.awt.image.*;
-import java.awt.event.*;
-import java.awt.font.*;
-import javax.imageio.*;
-import javax.swing.*;
-import java.util.*;
-import java.io.*;
-import game.*;
-import javax.sound.sampled.*;
-
-public class G
-{
-    public static final String version = "0.85";
-
-    public static final int numFonts = 4;
-    public static Font fonts[];
-    public static FontMetrics metrics[];
-
-    public static SoundManager smgr;
-
-    public static Dimension screenDim;
-
-    public static BufferedImage lautaBG = null, lautaBGScaled = null;
-}
--- a/src/IDMButton.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +0,0 @@
-/*
- * Ristipolku IDM button widget
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.awt.image.*;
-import java.awt.geom.*;
-import javax.imageio.*;
-import java.io.*;
-
-
-public class IDMButton extends IDMWidget
-{
-    enum State { FOCUSED, PRESSED, NORMAL }
-    State state;
-    static BufferedImage imgUp, imgPressed;
-    Font font;
-    FontMetrics metrics;
-    String text;
-    boolean active;
-
-    public IDMButton(IDMPoint pos, int keyCode, Font font, String text)
-    {
-        super(pos);
-        loadImages();
-        setSize(imgUp.getWidth(), imgUp.getHeight());
-
-        this.font = font;
-        this.keyCode = keyCode;
-        this.text = text;
-
-        state = State.NORMAL;
-        active = false;
-    }
-
-    public IDMButton(float x, float y, int keyCode, Font font, String text)
-    {
-        this(new IDMPoint(x, y), keyCode, font, text);
-    }
-
-    private static void loadImages()
-    {
-        if (imgUp != null && imgPressed != null)
-            return;
-
-        try
-        {
-            ResourceLoader res = new ResourceLoader("graphics/button1_up.png");
-            imgUp = ImageIO.read(res.getStream());
-
-            res = new ResourceLoader("graphics/button1_down.png");
-            imgPressed = ImageIO.read(res.getStream());
-        }
-        catch (IOException e)
-        {
-            System.out.print(e.getMessage());
-        }
-    }
-
-    public void scale()
-    {
-    }
-
-    public void paint(Graphics2D g)
-    {
-        BufferedImage img;
-        int xoffs, yoffs;
-
-        if (state == State.PRESSED)
-        {
-            img = imgPressed;
-            xoffs = yoffs = 5;
-        }
-        else
-        {
-            xoffs = yoffs = 0;
-            img = imgUp;
-        }
-
-        if (metrics == null)
-            metrics = g.getFontMetrics(font);
-
-        int textWidth = metrics.stringWidth(text);
-        g.drawImage(img, getScaledX() + xoffs, getScaledY() + yoffs, null);
-
-        g.setFont(font);
-        g.setPaint(Color.black);
-        g.drawString(text,
-           getScaledX() + xoffs + (getScaledWidth() - textWidth) / 2,
-           getScaledY() + yoffs + getScaledHeight() / 2);
-    }
-
-
-    public boolean mousePressed(MouseEvent e)
-    {
-        state = State.PRESSED;
-        active = true;
-        return true;
-    }
-
-    public boolean mouseReleased(MouseEvent e)
-    {
-        boolean oldActive = active;
-        super.mouseReleased(e);
-        state = State.NORMAL;
-        active = false;
-        return oldActive;
-    }
-
-    public boolean mouseEntered(MouseEvent e)
-    {
-        if (active)
-        {
-            state = State.PRESSED;
-            return true;
-        }
-        else
-            return false;
-    }
-
-    public boolean mouseExited(MouseEvent e)
-    {
-        if (active)
-        {
-            state = State.NORMAL;
-            return true;
-        }
-        else
-            return false;
-    }
-}
--- a/src/IDMContainer.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,190 +0,0 @@
-/*
- * Ristipolku IDM widget container
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.util.*;
-
-
-
-public class IDMContainer extends IDMWidget
-{
-    private ArrayList<IDMWidget> children, queue;
-    private IDMWidget modal;
-    private int iterated;
-    private boolean modified;
-
-    public IDMContainer()
-    {
-        children = new ArrayList<IDMWidget>();
-        iterated = 0;
-        modified = false;
-    }
-
-    synchronized public void add(IDMWidget widget)
-    {
-        widget.setParent(this);
-        if (iterated > 0)
-        {
-            queue.add(0, widget);
-            modified = true;
-        }
-        else
-            children.add(widget);
-    }
-
-    synchronized public void remove(IDMWidget widget)
-    {
-        widget.setParent(null);
-        if (iterated > 0)
-        {
-            queue.remove(widget);
-            modified = true;
-        }
-        else
-            children.remove(widget);
-    }
-
-    synchronized private void beginIteration()
-    {
-        modified = false;
-        if (iterated == 0)
-        {
-            queue = new ArrayList<IDMWidget>();
-            for (IDMWidget widget : children)
-                queue.add(widget);
-        }
-        iterated++;
-    }
-
-    synchronized private void endIteration()
-    {
-        if (modified && iterated == 1)
-        {
-            children = queue;
-            queue = null;
-        }
-        iterated--;
-    }
-
-    synchronized public void paint(Graphics2D g)
-    {
-        // Paint in reverse order
-        ListIterator<IDMWidget> iter = children.listIterator(children.size());
-
-        while (iter.hasPrevious())
-        {
-            final IDMWidget widget = iter.previous();
-            widget.paint(g);
-        }
-    }
-
-    synchronized public boolean mousePressed(MouseEvent e)
-    {
-        try {
-            beginIteration();
-            for (IDMWidget widget : children)
-            {
-                if (widget.contains(e.getPoint()))
-                {
-                    if (widget.mousePressed(e))
-                        return true;
-                }
-            }
-        }
-        finally { endIteration(); }
-        return false;
-    }
-
-    synchronized public boolean mouseReleased(MouseEvent e)
-    {
-        try {
-            beginIteration();
-            for (IDMWidget widget : children)
-            {
-                if (widget.mouseReleased(e))
-                    return true;
-            }
-        }
-        finally { endIteration(); }
-        return false;
-    }
-
-    synchronized public boolean mouseExited(MouseEvent e)
-    {
-        try {
-            beginIteration();
-            for (IDMWidget widget : children)
-            {
-                if (widget.mouseExited(e))
-                    return true;
-            }
-        }
-        finally { endIteration(); }
-        return false;
-    }
-
-    synchronized public boolean mouseEntered(MouseEvent e)
-    {
-        try {
-            beginIteration();
-            for (IDMWidget widget : children)
-            {
-                if (widget.contains(e.getPoint()))
-                {
-                    if (widget.mouseEntered(e))
-                        return true;
-                }
-            }
-        }
-        finally { endIteration(); }
-        return false;
-    }
-
-    synchronized public boolean keyPressed(KeyEvent e)
-    {
-        try {
-            beginIteration();
-            for (IDMWidget widget : children)
-            {
-                 if (widget.keyPressed(e))
-                    return true;
-            }
-        }
-        finally { endIteration(); }
-        return false;
-    }
-
-    synchronized public void setScale(IDMPoint scale)
-    {
-        beginIteration();
-        for (IDMWidget widget : children)
-        {
-            widget.setScale(scale);
-        }
-        endIteration();
-    }
-
-    synchronized public boolean hasObject(Class c)
-    {
-        for (IDMWidget widget : children)
-        {
-            if (widget.getClass() == c)
-                return true;
-        }
-        return false;
-    }
-
-    synchronized public boolean containsObject(Object o)
-    {
-        for (IDMWidget widget : children)
-        {
-            if (widget == o)
-                return true;
-        }
-        return false;
-    }
-}
--- a/src/IDMPoint.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-/*
- * Ristipolku IDM point
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-public class IDMPoint
-{
-    public float x, y;
-
-    public IDMPoint(float x, float y)
-    {
-        this.x = x;
-        this.y = y;
-    }
-
-    public IDMPoint(IDMPoint pt)
-    {
-        this.x = pt.x;
-        this.y = pt.y;
-    }
-
-    public IDMPoint copy()
-    {
-        return new IDMPoint(x, y);
-    }
-}
--- a/src/IDMWidget.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/*
- * Ristipolku IDM base widget
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.awt.*;
-import java.awt.event.*;
-
-
-public class IDMWidget
-{
-    IDMWidget parent;
-    IDMPoint pos, size, scale;
-    int keyCode;
-
-    public IDMWidget()
-    {
-        keyCode = -1;
-        this.scale = new IDMPoint(1, 1);
-        this.pos = new IDMPoint(0, 0);
-        this.size = new IDMPoint(0, 0);
-    }
-
-    public IDMWidget(IDMPoint pos)
-    {
-        this();
-        this.pos = pos;
-    }
-
-    public IDMWidget(IDMPoint pos, IDMPoint size)
-    {
-        this();
-        this.pos = pos;
-        this.size = size;
-    }
-
-    public void setParent(IDMWidget par)
-    {
-        this.parent = par;
-    }
-
-    public void add(IDMWidget widget)
-    {
-    }
-
-    public void remove(IDMWidget widget)
-    {
-    }
-
-    public void setPos(IDMPoint pos)
-    {
-        this.pos = pos;
-    }
-
-    public void setPos(float x, float y)
-    {
-        this.pos = new IDMPoint(x, y);
-    }
-
-    public void setSize(IDMPoint size)
-    {
-        this.size = size;
-    }
-
-    public void setSize(float w, float h)
-    {
-        this.size = new IDMPoint(w, h);
-    }
-
-    public void setScale(IDMPoint scale)
-    {
-        this.scale = scale;
-    }
-
-    public void setScale(float x, float y)
-    {
-        this.setScale(new IDMPoint(x, y));
-    }
-
-    public int getScaledX()
-    {
-        return (int) (pos.x * scale.x);
-    }
-
-    public int getScaledY()
-    {
-        return (int) (pos.y * scale.y);
-    }
-
-    public int getScaledWidth()
-    {
-        return (int) (size.x * scale.x);
-    }
-
-    public int getScaledHeight()
-    {
-        return (int) (size.y * scale.y);
-    }
-
-    public boolean contains(float x, float y)
-    {
-        return (x >= getScaledX() &&
-                y >= getScaledY() &&
-                x < getScaledX() + getScaledWidth() &&
-                y < getScaledY() + getScaledHeight());
-    }
-
-    public boolean contains(Point where)
-    {
-        return contains(where.x, where.y);
-    }
-
-    public boolean contains(IDMPoint where)
-    {
-        return contains(where.x, where.y);
-    }
-
-    public void paint(Graphics2D g)
-    {
-    }
-
-    public boolean mousePressed(MouseEvent e)
-    {
-        return false;
-    }
-
-    public boolean mouseReleased(MouseEvent e)
-    {
-        if (contains(e.getPoint()))
-        {
-            clicked();
-            return true;
-        }
-        return false;
-    }
-
-    public boolean mouseEntered(MouseEvent e)
-    {
-        return false;
-    }
-
-    public boolean mouseExited(MouseEvent e)
-    {
-        return false;
-    }
-
-    // Generic key handler
-    public boolean keyPressed(KeyEvent e)
-    {
-        if (e.getKeyCode() == keyCode)
-        {
-            clicked();
-            return true;
-        }
-        else
-            return false;
-    }
-
-    public void clicked()
-    {
-    }
-}
--- a/src/Interpolate.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-/*
- * Class for smooth non-linear interpolation between two given values in N steps
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.util.*;
-
-public class Interpolate
-{
-    public float start, end, steps;
-
-    public Interpolate(float start, float end, float steps)
-    {
-        this.start = start;
-        this.end = end;
-        this.steps = steps;
-    }
-
-    public float getValue(float step)
-    {
-        float n = step / steps;
-        float v = n * n * (3.0f - 2.0f * n);
-        return (start * v) + (end * (1.0f - v));
-    }
-}
--- a/src/Piece.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,365 +0,0 @@
-/*
- * Ristipolku
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.awt.*;
-import java.awt.geom.*;
-import java.util.*;
-import java.math.*;
-
-
-public class Piece
-{
-    public enum RotateDir { LEFT, RIGHT }
-
-    static final int numConnections = 8;
-    static final float maxTime = 50.0f;
-
-    int currRotation;
-    int[] connections;
-    boolean[] states;
-    PieceType type, prevType;
-
-    boolean rotationChanged, rotationActive,
-            typeChanged, typeActive,
-            stateChanged, stateActive,
-            active;
-
-    float   currAngle, rotationTime, rotationSpeed,
-            typeTime, typeValue, throbTime;
-
-    Interpolate lerpRotation,
-                lerpScale,
-                lerpType;
-
-
-    public Piece(PieceType ptype)
-    {
-        // Initialize
-        connections = new int[numConnections];
-        states = new boolean[numConnections];
-        type = ptype;
-
-        rotationChanged = false;
-        rotationActive = false;
-        currRotation = 4 * 5000;
-        currAngle = getAngle(currRotation);
-
-        typeChanged = false;
-        typeActive = false;
-
-        throbTime = 0;
-
-        lerpType  = new Interpolate(1.0f, 0.0f, maxTime);
-        lerpScale = new Interpolate(0.0f, (float) Math.PI, maxTime);
-
-        // Initialize connections between endpoints of the paths inside the piece
-        for (int i = 0; i < numConnections; i++)
-            connections[i] = -1;
-
-
-        // Randomize connections in the piece
-        Random rnd = new Random();
-        for (int i = 0; i < numConnections; i++)
-        {
-            while (connections[i] < 0)
-            {
-                int tmp = rnd.nextInt(numConnections);
-                if (tmp != i && connections[tmp] < 0)
-                {
-                    connections[i] = tmp;
-                    connections[tmp] = i;
-                }
-            }
-        }
-    }
-
-    public Piece()
-    {
-        this(PieceType.NONE);
-    }
-
-
-    public void changed()
-    {
-        typeChanged = true;
-    }
-
-    public void setType(PieceType ptype)
-    {
-//        typeChanged = (prevType != ptype) && (ptype == PieceType.LOCKED);
-        prevType = type;
-        type = ptype;
-    }
-
-    public void clearStates()
-    {
-        for (int i = 0; i < numConnections; i++)
-            states[i] = false;
-        stateChanged = true;
-    }
-
-    public int getRotation()
-    {
-        return currRotation % 4;
-    }
-
-    public int getRotatedPoint(int in)
-    {
-        int point = (in - (getRotation() * 2)) % 8;
-        if (point < 0) point = 8 + point;
-        return point;
-    }
-
-    public int getAntiRotatedPoint(int in)
-    {
-        int point = (in + (getRotation() * 2)) % 8;
-        if (point < 0) point = 8 + point;
-        return point;
-    }
-
-    public int getMatchingPoint(int point)
-    {
-        switch (point)
-        {
-            case 0: return 5;
-            case 1: return 4;
-
-            case 2: return 7;
-            case 3: return 6;
-
-            case 4: return 1;
-            case 5: return 0;
-
-            case 6: return 3;
-            case 7: return 2;
-        }
-        return -1;
-    }
-
-    public void setConnectionState(int point, boolean state)
-    {
-        states[point] = state;
-        states[connections[point]] = state;
-        stateChanged = true;
-    }
-
-    public int getConnection(int point)
-    {
-        return connections[point];
-    }
-
-    private float getAngle(float rotation)
-    {
-        return (float) ((rotation * Math.PI) / 2.0f);
-    }
-
-    public void rotate(RotateDir dir)
-    {
-        // Only normal
-        if (type != PieceType.LOCKED && type != PieceType.ACTIVE)
-            return;
-
-        currRotation = (currRotation + (dir == RotateDir.RIGHT ? 1 : -1));
-        lerpRotation = new Interpolate(getAngle(currRotation), currAngle, maxTime);
-        rotationChanged = true;
-    }
-
-    public Point2D getPointCoords(float x, float y, float dim, int index)
-    {
-        float ox = 0, oy = 0;
-        float step = dim / 10;
-
-        switch (index) {
-            // Normal line starting and ending points
-            case  0: ox = 3.0f; oy = 0.4f; break;
-            case  1: ox = 7.0f; oy = 0.4f; break;
-
-            case  2: ox = 9.6f; oy = 3.0f; break;
-            case  3: ox = 9.6f; oy = 7.0f; break;
-
-            case  4: ox = 7.0f; oy = 9.6f; break;
-            case  5: ox = 3.0f; oy = 9.6f; break;
-
-            case  6: ox = 0.4f; oy = 7.0f; break;
-            case  7: ox = 0.4f; oy = 3.0f; break;
-
-
-            // Matching control points for each point above (+8)
-            case  8: ox = 3.0f; oy = 2.5f; break;
-            case  9: ox = 7.0f; oy = 2.5f; break;
-
-            case 10: ox = 7.5f; oy = 3.0f; break;
-            case 11: ox = 7.5f; oy = 7.0f; break;
-
-            case 12: ox = 7.0f; oy = 7.5f; break;
-            case 13: ox = 3.0f; oy = 7.5f; break;
-
-            case 14: ox = 2.5f; oy = 7.0f; break;
-            case 15: ox = 2.5f; oy = 3.0f; break;
-        }
-
-        return new Point2D.Float(x + ox * step, y + oy * step);
-    }
-
-    public PieceType getType()
-    {
-        return type;
-    }
-
-    public void animate(float time)
-    {
-        active = false;
-
-        if (rotationChanged)
-        {
-            rotationTime = time;
-            rotationActive = true;
-            rotationChanged = false;
-            rotationSpeed = 1.0f;
-        }
-
-        if (typeChanged && type == PieceType.LOCKED)
-        {
-            rotationSpeed = 1.5f;
-        }
-
-        if (rotationActive)
-        {
-            float t = (time - rotationTime) * rotationSpeed;
-
-            if (t < maxTime)
-            {
-                currAngle = lerpRotation.getValue(t);
-            }
-            else
-            {
-                currAngle = lerpRotation.start;
-                rotationActive = false;
-            }
-
-            active = true;
-        }
-
-        if (typeChanged)
-        {
-            typeTime = time;
-            typeActive = true;
-            typeChanged = false;
-        }
-
-        if (typeActive)
-        {
-            float t = (time - typeTime) * 2.0f;
-
-            if (t < maxTime)
-            {
-                typeValue = lerpType.getValue(t);
-            }
-            else
-            {
-                typeValue = lerpType.start;
-                typeActive = false;
-            }
-
-
-            active = true;
-        }
-
-        if (stateChanged)
-        {
-        }
-
-        throbTime = (time % 100) / 100.0f;
-    }
-
-    public void paint(Graphics2D g, float x, float y, float dim)
-    {
-        AffineTransform save = g.getTransform();
-        Composite csave = g.getComposite();
-
-
-        // Change compositing alpha for the whole piece drawing
-        // when the piece is being "introduced".
-        if (typeActive)
-        {
-            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, typeValue));
-        }
-
-
-        // Transform drawing by current angle
-        g.rotate(currAngle, x + dim / 2.0f, y + dim / 2.0f);
-
-        // Color piece by type
-        switch (type) {
-            case LOCKED:  g.setPaint(new Color(0.3f, 0.8f, 0.3f, 0.35f)); break;
-            case ACTIVE:  g.setPaint(new Color(0.9f, 0.3f, 0.3f, 0.35f)); break;
-            case START:   g.setPaint(new Color(1.0f, 0.6f, 0.0f, 0.95f)); break;
-        }
-
-        float corner = dim / 10.0f;
-        g.fill(new RoundRectangle2D.Float(x, y, dim, dim, corner, corner));
-
-        // Start pieces (center piece) have a different kind of border
-        // and no connections drawn inside
-        if (type == PieceType.START)
-        {
-            // Draw piece border
-            g.setPaint(Color.black);
-            g.setStroke(new BasicStroke(2.0f));
-            g.draw(new RoundRectangle2D.Float(x, y, dim, dim, corner, corner));
-        }
-        else
-        {
-            // Active piece has a throbbing "ghost" border
-            if (type == PieceType.ACTIVE)
-            {
-                float offs1 = throbTime * 10.0f,
-                      offs2 = throbTime * 20.0f;
-
-                g.setPaint(new Color(0.0f, 0.0f, 0.0f, (float) (1.0f - throbTime) ));
-                g.setStroke(new BasicStroke(2.0f + throbTime * 2.0f));
-                g.draw(new RoundRectangle2D.Float(
-                    x - offs1, y - offs1,
-                    dim + offs2, dim + offs2,
-                    corner, corner));
-            }
-
-            // Draw piece border
-            g.setPaint(new Color(0.0f, 0.0f, 0.0f, 0.6f));
-            g.setStroke(new BasicStroke(5.0f));
-            g.draw(new RoundRectangle2D.Float(x, y, dim, dim, dim / 10, dim / 10));
-
-            // Draw the connections
-            g.setStroke(new BasicStroke(5.5f));
-            CubicCurve2D curve = new CubicCurve2D.Float();
-            boolean[] drawn = new boolean[numConnections];
-            for (int i = 0; i < numConnections; i++)
-            if (!drawn[i])
-            {
-                Point2D start, cp1, cp2, end;
-                boolean isActive = states[i] || states[connections[i]];
-
-                g.setPaint(isActive ? Color.white : Color.black);
-
-                start = getPointCoords(x, y, dim, i);
-                end   = getPointCoords(x, y, dim, connections[i]);
-                cp1   = getPointCoords(x, y, dim, i + 8);
-                cp2   = getPointCoords(x, y, dim, connections[i] + 8);
-
-                curve.setCurve(start, cp1, cp2, end);
-                g.draw(curve);
-
-                // Mark connection drawn, so we don't overdraw
-                drawn[i] = true;
-                drawn[connections[i]] = true;
-            }
-
-        } // !PieceType.START
-
-        g.setTransform(save);
-        g.setComposite(csave);
-    }
-}
--- a/src/PieceType.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-/*
- * Ristipolku
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-public enum PieceType { START, LOCKED, ACTIVE, NONE }
-
--- a/src/ResourceLoader.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-/*
- * Ristipolku Resource Loader
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.util.*;
-import java.io.*;
-import java.net.*;
-
-
-public class ResourceLoader
-{
-    InputStream stream;
-    String name;
-    URL resourceURL;
-
-    public ResourceLoader(String name)
-    {
-        this.name = name;
-        resourceURL = getClass().getClassLoader().getResource(name);
-        if (resourceURL != null)
-            stream = getClass().getClassLoader().getResourceAsStream(name);
-
-        System.out.print("ResourceLoader('"+ name +"'): "+ resourceURL +" - "+ stream +"\n");
-    }
-
-    public InputStream getStream()
-    {
-        return stream;
-    }
-
-    public URL getURL()
-    {
-        return resourceURL;
-    }
-}
--- a/src/Sound.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-package game;
-
-public class Sound {
-
-    private byte[] samples;
-
-    public Sound(byte[] samples)
-    {
-        this.samples = samples;
-    }
-
-    public byte[] getSamples()
-    {
-        return samples;
-    }
-
-}
--- a/src/SoundManager.java	Tue Jun 21 12:53:53 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,459 +0,0 @@
-/*
- * Ristipolku Game Engine
- * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
- */
-package game;
-
-import java.util.*;
-import java.io.*;
-import game.*;
-import javax.sound.sampled.*;
-
-
-public class SoundManager extends ThreadGroup
-{
-    private boolean alive;
-    private LinkedList queue;
-    private int id;
-    private static int poolID;
-
-    private AudioFormat playbackFormat;
-    private ThreadLocal localLine;
-    private ThreadLocal localBuffer;
-    private Object pausedLock;
-    private boolean paused;
-
-
-    public SoundManager(AudioFormat format)
-    {
-        this(format, getMaxSimultaneousSounds(format));
-    }
-
-
-    public SoundManager(AudioFormat format, int maxSounds)
-    {
-        super("SoundManagerPool-" + (poolID++));
-
-        int numThreads = Math.min(maxSounds, getMaxSimultaneousSounds(playbackFormat));
-
-        System.out.print("SMGR.SoundManager() initializing with " + numThreads +" max sounds\n");
-
-        setDaemon(true);
-        alive = true;
-
-        playbackFormat = format;
-        localLine = new ThreadLocal();
-        localBuffer = new ThreadLocal();
-        pausedLock = new Object();
-
-        queue = new LinkedList();
-        for (int i = 0; i < numThreads; i++)
-            new PooledThread().start();
-
-        synchronized (this)
-        {
-            notifyAll();
-        }
-    }
-
-
-    public static int getMaxSimultaneousSounds(AudioFormat playbackFormat)
-    {
-        DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
-
-        Mixer.Info[] info = AudioSystem.getMixerInfo();
-        System.out.print("SMGR.getMaxSimultaneousSounds() mixer information:\n");
-        Mixer.Info select = null;
-        for (Mixer.Info i : info)
-        {
-            System.out.print("  - '"+i.getName()+"'\n");
-            if (i.getName().equals("Java Sound Audio Engine"))
-                select = i;
-        }
-
-        Mixer mixer = AudioSystem.getMixer(select);
-
-        Mixer.Info i = mixer.getMixerInfo();
-        System.out.print("  * selected='"+i.getName()+"'\n");
-
-        int maxLines = mixer.getMaxLines(lineInfo);
-        if (maxLines == AudioSystem.NOT_SPECIFIED)
-            maxLines = 8;
-
-        System.out.print("  * maxLines="+maxLines+"\n");
-
-        return maxLines;
-    }
-
-
-    protected void cleanUp()
-    {
-        System.out.print("SMGR.cleanUp()\n");
-        // signal to unpause
-        setPaused(false);
-
-        System.out.print("SMGR.cleanUp(): closing mixer\n");
-
-        // close the mixer (stops any running sounds)
-        Mixer mixer = AudioSystem.getMixer(null);
-        if (mixer.isOpen())
-            mixer.close();
-
-        System.out.print("SMGR.cleanUp(): leaving\n");
-    }
-
-
-    public void setPaused(boolean paused)
-    {
-        if (this.paused != paused)
-        {
-            synchronized (pausedLock)
-            {
-                this.paused = paused;
-                if (!paused)
-                {
-                    // restart sounds
-                    pausedLock.notifyAll();
-                }
-            }
-        }
-    }
-
-
-    public boolean isPaused()
-    {
-        return paused;
-    }
-
-
-    public Sound getSound(String filename)
-    {
-        return getSound(getAudioInputStream(filename));
-    }
-
-
-    public Sound getSound(InputStream is)
-    {
-        return getSound(getAudioInputStream(is));
-    }
-
-
-    public Sound getSound(AudioInputStream audioStream)
-    {
-        if (audioStream == null)
-            return null;
-
-        // get the number of bytes to read
-        int length = (int)(audioStream.getFrameLength() * audioStream.getFormat().getFrameSize());
-
-        // read the entire stream
-        byte[] samples = new byte[length];
-        DataInputStream is = new DataInputStream(audioStream);
-        try {
-            is.readFully(samples);
-            is.close();
-        }
-        catch (IOException ex)
-        {
-            ex.printStackTrace();
-        }
-
-        // return the samples
-        return new Sound(samples);
-    }
-
-
-    public AudioInputStream getAudioInputStream(String filename)
-    {
-        ResourceLoader res = new ResourceLoader(filename);
-        if (res == null || res.getStream() == null)
-        {
-            System.out.print("Could not load audio resource '"+ filename +"'.\n");
-            return null;
-        }
-        try {
-            return getAudioInputStream(res.getStream());
-        }
-        catch (Exception ex)
-        {
-            System.out.print("Could not get AudioInputStream for '"+ filename +"'\n");
-            return null;
-        }
-    }
-
-
-    public AudioInputStream getAudioInputStream(InputStream is)
-    {
-        try {
-            if (!is.markSupported())
-                is = new BufferedInputStream(is);
-
-            // open the source stream
-            AudioInputStream source = AudioSystem.getAudioInputStream(is);
-
-            // convert to playback format
-            return AudioSystem.getAudioInputStream(playbackFormat, source);
-        }
-
-        catch (UnsupportedAudioFileException ex) {
-            ex.printStackTrace();
-        }
-        catch (IOException ex) {
-            ex.printStackTrace();
-        }
-        catch (IllegalArgumentException ex) {
-            ex.printStackTrace();
-        }
-
-        return null;
-    }
-
-
-    public InputStream play(Sound sound)
-    {
-        InputStream is;
-        if (sound != null)
-        {
-            is = new ByteArrayInputStream(sound.getSamples());
-            return play(is);
-        }
-        return null;
-    }
-
-
-    public InputStream play(InputStream is)
-    {
-        System.out.print("SMGR.play(is="+is+")\n");
-        if (is != null)
-        {
-            runTask(new SoundPlayer(is));
-        }
-        return is;
-    }
-
-
-    protected void threadStarted()
-    {
-        synchronized (this)
-        {
-            try {
-                wait();
-            }
-            catch (InterruptedException ex) { }
-        }
-
-
-        // use a short, 100ms (1/10th sec) buffer for filters that
-        // change in real-time
-        int bufferSize = playbackFormat.getFrameSize() * Math.round(playbackFormat.getSampleRate() / 10);
-
-        // create, open, and start the line
-        SourceDataLine line;
-        DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
-
-        System.out.print("SMGR.threadStarted(): "+lineInfo.toString()+"\n");
-
-        try {
-            line = (SourceDataLine) AudioSystem.getLine(lineInfo);
-            line.open(playbackFormat, bufferSize);
-        }
-        catch (LineUnavailableException ex)
-        {
-            System.out.print("SMGR.threadStarted() line unavailable!\n");
-            // the line is unavailable - signal to end this thread
-            Thread.currentThread().interrupt();
-            return;
-        }
-
-        // Change volume
-        Control.Type ct = FloatControl.Type.MASTER_GAIN;
-        if (line.isControlSupported(ct))
-        {
-            FloatControl c = (FloatControl) line.getControl(ct);
-            c.setValue(-20f);
-        }
-
-        line.start();
-
-        // create the buffer
-        byte[] buffer = new byte[bufferSize];
-
-        // set this thread's locals
-        localLine.set(line);
-        localBuffer.set(buffer);
-    }
-
-
-    protected void threadStopped()
-    {
-        System.out.print("SMGR.threadStopped()\n");
-        SourceDataLine line = (SourceDataLine) localLine.get();
-        if (line != null)
-        {
-            line.drain();
-            line.close();
-        }
-    }
-
-
-    protected class SoundPlayer implements Runnable
-    {
-        private InputStream source;
-
-        public SoundPlayer(InputStream source)
-        {
-            this.source = source;
-        }
-
-        public void run()
-        {
-            // get line and buffer from ThreadLocals
-            SourceDataLine line = (SourceDataLine) localLine.get();
-            byte[] buffer = (byte[])localBuffer.get();
-
-            if (line == null || buffer == null)
-                return;
-
-            // copy data to the line
-            try {
-                boolean playing = true;
-                while (playing) {
-                    // if paused, wait until unpaused
-                    synchronized (pausedLock)
-                    {
-                        if (paused) {
-                            try {
-                                pausedLock.wait();
-                            }
-                            catch (InterruptedException ex) {
-                                return;
-                            }
-                        }
-                    }
-
-                    // copy data
-                    int bufPos = 0;
-                    while (bufPos < buffer.length && playing)
-                    {
-                        int res = source.read(buffer, bufPos, buffer.length - bufPos);
-                        if (res != -1)
-                            bufPos += res;
-                        else
-                            playing = false;
-                    }
-                    if (playing)
-                        line.write(buffer, 0, bufPos);
-                }
-            }
-
-            catch (IOException ex) {
-                ex.printStackTrace();
-            }
-
-        }
-    }
-
-    public synchronized void runTask(Runnable task)
-    {
-        if (!alive)
-        {
-            throw new IllegalStateException();
-        }
-        if (task != null)
-        {
-            queue.add(task);
-            notify();
-        }
-
-    }
-
-    protected synchronized Runnable getTask() throws InterruptedException
-    {
-        while (queue.size() == 0)
-        {
-            if (!alive)
-                return null;
-
-            wait();
-        }
-        return (Runnable) queue.removeFirst();
-    }
-
-
-    public synchronized void close()
-    {
-        System.out.print("SMGR.close()\n");
-
-        if (alive)
-        {
-            System.out.print("SMGR.close(): alive queue clear\n");
-            // Clear queue
-            alive = false;
-            queue.clear();
-            interrupt();
-        }
-
-        cleanUp();
-        System.out.print("SMGR.close(): leaving\n");
-    }
-
-
-    public void join()
-    {
-        System.out.print("SMGR.join()\n");
-        cleanUp();
-
-        synchronized (this)
-        {
-            alive = false;
-            notifyAll();
-        }
-
-        Thread[] threads = new Thread[activeCount()];
-        int count = enumerate(threads);
-        for (int i = 0; i < count; i++)
-        {
-            try {
-                threads[i].join();
-            }
-            catch (InterruptedException ex)
-            {
-            }
-        }
-    }
-
-
-    private class PooledThread extends Thread
-    {
-        public PooledThread()
-        {
-            super(SoundManager.this, "SoundManagerPool-" + (id++));
-        }
-
-        public void run()
-        {
-            threadStarted();
-
-            while (!isInterrupted()) {
-                Runnable task = null;
-                try {
-                    task = getTask();
-                }
-                catch (InterruptedException ex)
-                {
-                }
-
-                if (task == null)
-                    break;
-
-                try {
-                    task.run();
-                }
-                catch (Throwable t) {
-                    uncaughtException(this, t);
-                }
-            }
-            threadStopped();
-        }
-    }
-}