Mercurial > hg > ristipolku
view 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 source
/* * 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); } } }