Mercurial > hg > ristipolku
diff src/Engine.java @ 161:fb33d3796942
Rename source directory.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 21 Jun 2016 12:53:53 +0300 |
parents | game/Engine.java@1ba6f56203f2 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Engine.java Tue Jun 21 12:53:53 2016 +0300 @@ -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); + } + } +}