/** * Game "Lines" * * * * * The applet needs JDK 1.1. * Tested in RedHat Linux (JDK 1.1.3, 1.1.5, 1.1.6) and * under Win95, JDK 1.1.6 as standalone Java application. * Applet is tested under Netscape 4.5. * * Written as a simple game example in Java. * The source is free. * * vvb@mech.math.msu.su, 1999 */ import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.applet.Applet; import java.io.*; public class Lines extends Applet implements FilenameFilter { static final int DX = 30; static final int DY = 30; static final int GRIDX = 3; static final int GRIDY = 3; static final int MARGINX = 10; static final int MARGINY = 5; static final int SKIPX = 10; static final int SKIPY = 10; static final int SCORE_DY = 20; static final int SCORE_FONT_SIZE = 12; static final int BAR_DX = 25; static final int BAR_LEFT_SKIP = 10; static final int BAR_RIGHT_SKIP = 25; static final int SCORE_DX = (BAR_DX+BAR_RIGHT_SKIP); static final int BALL_DX = 2; static final int BALL_DY = 2; static final int ACTIVE_BALL_DX = 2; static final int ACTIVE_BALL_DY = 2; static final int DT = 180; // Time interval in msec // Score int scoreX; int scoreY; int scoreTop; static final int INITIAL_RECORD = 100; static final int BONUS = 1; static final int BONUS2 = 1; // For each ball above 5 static final int BONUS3 = 2; // For each ball in crossed rows Font scoreFont = new Font("Helvetica", Font.BOLD, SCORE_FONT_SIZE); int scoreBarX; int scoreBarY0; int scoreBarY1; int recordX; int recordY; int recordTop; int recordBarX; int recordBarY0; int recordBarY1; // Field dimensions static int WIDTH = 10; static int HEIGHT = 10; // Field byte field[][] = new byte[WIDTH][HEIGHT]; int fieldX; int fieldY; // Path to the new cell byte path[][] = new byte[WIDTH*HEIGHT][2]; int pathLength = 0; int paths[][] = new int[WIDTH][HEIGHT]; // Rows to remove byte topBottom[][] = new byte[2][2]; // [beg-end][x-y] int topBottomLen = 0; byte leftRight[][] = new byte[2][2]; int leftRightLen = 0; byte leftDiag[][] = new byte[2][2]; // top-left --> bottom-right int leftDiagLen = 0; byte rightDiag[][] = new byte[2][2]; // left-bottom --> top-right int rightDiagLen = 0; static final int EMPTY = 127; int numBalls = 0; int numFree = WIDTH * HEIGHT; int balls3X; int balls3Y; // Colors static final int NUM_COLORS = 7; Color colors[] = new Color[NUM_COLORS + 1]; Color BG_COLOR = Color.lightGray; Color FIELD_BG_COLOR = Color.gray; Color GRID_COLOR = Color.yellow; Color REMOVED_COLOR = Color.white; Color SCORE_COLOR = Color.red; Color SCORE_BAR_COLOR = Color.red; Color SCORE_BG_COLOR = Color.white; Color RECORD_COLOR = Color.blue; Color RECORD_BAR_COLOR = Color.blue; // Ball pictures Image ballImage[] = new Image[NUM_COLORS + 1]; Image smallBallImage[] = new Image[NUM_COLORS]; Image nextBallImage[] = new Image[NUM_COLORS]; // Image file names String ballImageFile[] = new String[2*NUM_COLORS + 1]; MediaTracker tracker; boolean imageError = true; BgFilter bgFilter; FieldBgFilter fieldBgFilter; // New 3 balls int newColors[] = new int[3]; // Active ball int activeBallX; int activeBallY; boolean ballActive; boolean activeBallBig; // Blinking: big -- small -- big -- small boolean userMove; boolean bonusMove; // Score int score = 0; int record = INITIAL_RECORD; // Record table static final int RECORD_TABLE_LEN = 10; // Record table int records[] = new int[RECORD_TABLE_LEN]; String recordNames[] = new String[RECORD_TABLE_LEN]; int recordTableLen = 0; static final String RECORD_FILE = "Lines.dat"; String userName = ""; boolean userNameOK = false; Frame parentFrame = null; boolean showRecordTable = false; boolean scoreWrittenInRecordTable = false; Button skipMove; Button newGame; int movingThreadRunning = 0; boolean activeBallThreadRunning = false; boolean gameOver = false; // Offscreen Image offscreenImage = null; Graphics offscreenGraphics = null; Rectangle offscreenRect = new Rectangle(0, 0, 0, 0); // Load/save String directory = null; String fileName = null; public static void main(String[] args) { Frame f = new Frame("Lines"); f.setFont(new Font("Helvetica", Font.PLAIN, 14)); // Add menu bar final MenuBar mb = new MenuBar(); f.setMenuBar(mb); final Menu fileMenu = new Menu("File"); final MenuItem newItem = new MenuItem("New Game"); final MenuItem loadItem = new MenuItem("Load Game"); final MenuItem saveItem = new MenuItem("Save Game"); final MenuItem recordItem = new MenuItem("Record Table"); final MenuItem quitItem = new MenuItem("Quit"); fileMenu.add(newItem); fileMenu.add(loadItem); fileMenu.add(saveItem); fileMenu.add(recordItem); fileMenu.add(quitItem); mb.add(fileMenu); Lines lines = new Lines(); f.add("Center", lines); //... f.setBounds(30, 5, 560, 507); f.setBounds( 30, 5, MARGINX + BAR_LEFT_SKIP + BAR_DX + BAR_RIGHT_SKIP + SKIPX + GRIDX + WIDTH*(GRIDX + DX) + SKIPX + SCORE_DX + 2*MARGINX, MARGINY + DY + SKIPY + GRIDY + HEIGHT*(GRIDY + DY) + MARGINY + 60 ); lines.parentFrame = f; lines.init(); lines.loadRecordTable(); lines.start(); final Lines game = lines; ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { // System.out.println("Action " + e); if (e.getActionCommand().equals("New Game")) { game.onNewGame(); } else if (e.getActionCommand().equals("Load Game")) { game.onLoadGame(); } else if (e.getActionCommand().equals("Save Game")) { game.onSaveGame(); } else if (e.getActionCommand().equals("Record Table")) { game.onRecordTable(); } else if (e.getActionCommand().equals("Quit")) { game.onQuit(); } } }; fileMenu.addActionListener(al); final Lines l = lines; WindowAdapter wa = new WindowAdapter() { public void windowClosing(WindowEvent e) { l.onQuit(); } }; f.addWindowListener(wa); f.setVisible(true); } public void init() { int x, y; setLayout(null); setBackground(BG_COLOR); // Fill in the colors array colors[0] = Color.red; colors[1] = Color.pink; colors[2] = Color.blue; colors[3] = Color.magenta; colors[4] = Color.green; colors[5] = Color.yellow; colors[6] = Color.cyan; colors[7] = Color.white; ballImageFile[0] = "RedBall.gif"; ballImageFile[1] = "SmallRedBall.gif"; ballImageFile[2] = "PinkBall.gif"; ballImageFile[3] = "SmallPinkBall.gif"; ballImageFile[4] = "BlueBall.gif"; ballImageFile[5] = "SmallBlueBall.gif"; ballImageFile[6] = "MagentaBall.gif"; ballImageFile[7] = "SmallMagentaBall.gif"; ballImageFile[8] = "GreenBall.gif"; ballImageFile[9] = "SmallGreenBall.gif"; ballImageFile[10] = "YellowBall.gif"; ballImageFile[11] = "SmallYellowBall.gif"; ballImageFile[12] = "CyanBall.gif"; ballImageFile[13] = "SmallCyanBall.gif"; ballImageFile[14] = "BlownBall.gif"; tracker = new MediaTracker(this); bgFilter = new BgFilter(); fieldBgFilter = new FieldBgFilter(); // Load images for (int i = 0; i < NUM_COLORS + 1; i++) { Image ball, smallBall = null, nextBall; if (parentFrame == null) { // It is an applet ball = getImage(getCodeBase(), ballImageFile[2*i]); if (i < NUM_COLORS) { smallBall = getImage(getCodeBase(), ballImageFile[2*i + 1]); } } else { ball = Toolkit.getDefaultToolkit().getImage( ballImageFile[2*i] ); if (i < NUM_COLORS) { smallBall = Toolkit.getDefaultToolkit().getImage( ballImageFile[2*i + 1] ); } } ImageProducer fieldBallProducer = new FilteredImageSource( ball.getSource(), fieldBgFilter ); ballImage[i] = createImage(fieldBallProducer); if (i < NUM_COLORS) { ImageProducer smallBallProducer = new FilteredImageSource( smallBall.getSource(), fieldBgFilter ); smallBallImage[i] = createImage(smallBallProducer); ImageProducer nextBallProducer = new FilteredImageSource( ball.getSource(), bgFilter ); nextBallImage[i] = createImage(nextBallProducer); } tracker.addImage(ballImage[i], 0); if (i < NUM_COLORS) { tracker.addImage(nextBallImage[i], 0); tracker.addImage(smallBallImage[i], 1); } } newColors[0] = 0; newColors[1] = 1; newColors[2] = 2; // Layout fieldX = MARGINX + BAR_LEFT_SKIP + BAR_DX + BAR_RIGHT_SKIP + SKIPX; fieldY = MARGINY + DY + SKIPY; balls3X = MARGINX + BAR_LEFT_SKIP + BAR_DX + BAR_RIGHT_SKIP + SKIPX; balls3Y = fieldY - (DY + SKIPY); recordBarX = MARGINX + BAR_LEFT_SKIP; recordBarY0 = fieldY; recordBarY1 = fieldY + GRIDY + HEIGHT * (DY + GRIDY); scoreBarX = fieldX + GRIDX + WIDTH * (DX + GRIDX) + BAR_LEFT_SKIP + SKIPX; scoreBarY0 = recordBarY0; scoreBarY1 = recordBarY1; recordX = MARGINX; // recordY = fieldY - DY; recordY = balls3Y + (DY - SCORE_DY)/2; scoreX = scoreBarX - BAR_LEFT_SKIP + SKIPX; scoreY = recordY; // System.out.println( // "Applet size: width=" + (scoreX + SCORE_DX + MARGINX) + // " height=" + (fieldY + (GRIDY + DY)*HEIGHT + GRIDY + MARGINY) // ); MouseAdapter ma = new MouseAdapter() { public void mousePressed(MouseEvent e) { if (showRecordTable) { showRecordTable = false; drawInOffscreen(); repaint(); } else { Point p = e.getPoint(); onMouseDown(p.x, p.y); } } }; addMouseListener(ma); // Buttons skipMove = new Button("Skip"); add(skipMove); newGame = new Button("New Game"); add(newGame); newGame.setFont(scoreFont); skipMove.setFont(scoreFont); x = balls3X + 3*(DX + GRIDX) + SKIPX; newGame.setBounds(x, scoreY, 3*DX, SCORE_DY); x += 3*DX + SKIPX; skipMove.setBounds(x, scoreY, 2*DX, SCORE_DY); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (source == skipMove) { onSkip(); } else if (source == newGame) { onNewGame(); } } }; newGame.addActionListener(al); skipMove.addActionListener(al); try { tracker.waitForAll(); } catch (InterruptedException e) { } imageError = (!tracker.checkAll() || tracker.isErrorAny()); initGame(); } public void paint(Graphics g) { update(g); } public void update(Graphics g) { Rectangle r = getBounds(); if ( offscreenImage == null || offscreenRect.width != r.width || offscreenRect.height != r.height ) { offscreenImage = createImage(r.width, r.height); offscreenGraphics = offscreenImage.getGraphics(); offscreenRect = r; drawInOffscreen(); } g.drawImage(offscreenImage, 0, 0, this); } void drawInOffscreen() { Graphics g = offscreenGraphics; if (g == null) return; Rectangle r = getBounds(); g.setColor(BG_COLOR); g.fillRect(0, 0, r.width, r.height); int fieldWidth = (GRIDX + DX)*WIDTH + GRIDX; int fieldHeight = (GRIDY + DY)*HEIGHT + GRIDY; g.fillRect(0, 0, fieldX, r.height); g.fillRect(fieldX, 0, r.width, fieldY); g.fillRect( fieldX + fieldWidth, fieldY, r.width - (fieldX + fieldWidth), r.height - fieldY ); g.fillRect( fieldX, fieldY + fieldHeight, r.width - fieldX, r.height - (fieldY + fieldHeight) ); drawField(g); drawRecord(g); drawScore(g); draw3NewBalls(g); if (gameOver) drawGameOver(g); if (showRecordTable) showRecords(g); } void drawOffscreen() { if (offscreenImage == null) { repaint(); } else { Graphics g = getGraphics(); g.drawImage(offscreenImage, 0, 0, this); } } void drawField(Graphics g) { int i, x, y; imageError = (!tracker.checkAll() || tracker.isErrorAny()); // System.out.println("isErrorAny returned " + imageError); // Field rectangle int rx = fieldX; int ry = fieldY; int rwidth = (GRIDX + DX)*WIDTH + GRIDX; int rheight = (GRIDY + DY)*HEIGHT + GRIDY; g.setColor(FIELD_BG_COLOR); g.fillRect(rx, ry, rwidth, rheight); // Outline a field rectangle g.setColor(Color.black); g.drawRect(rx, ry, rwidth, rheight); // Vertical grid g.setColor(GRID_COLOR); rx = fieldX; rwidth = GRIDX; for (i = 0; i <= WIDTH; i++) { g.fillRect(rx, ry, rwidth, rheight); rx += (DX + GRIDX); } rx = fieldX; rwidth = (GRIDX + DX)*WIDTH + GRIDX; ry = fieldY; rheight = GRIDY; for (i = 0; i <= HEIGHT; i++) { g.fillRect(rx, ry, rwidth, rheight); ry += (DY + GRIDY); } for (x = 0; x < WIDTH; x++) { for (y = 0; y < HEIGHT; y++) { int col = field[x][y]; if (col != EMPTY) { col %= (NUM_COLORS + 1); rx = fieldX + GRIDX + x * (DX + GRIDX) + BALL_DX; rwidth = DX - 2 * BALL_DX; ry = fieldY + GRIDY + y * (DY + GRIDY) + BALL_DY; rheight = DY - 2 * BALL_DY; if ( ballActive && x == activeBallX && y == activeBallY ) { if (activeBallBig) { rx += ACTIVE_BALL_DX; ry += ACTIVE_BALL_DY; rwidth -= 2*ACTIVE_BALL_DX; rheight -= 2*ACTIVE_BALL_DY; } } if (imageError) { g.setColor(colors[col]); g.fillOval(rx, ry, rwidth, rheight); g.setColor(Color.black); g.drawOval(rx, ry, rwidth, rheight); } else { if ( ballActive && x == activeBallX && y == activeBallY ) { g.drawImage( smallBallImage[col], rx, ry, FIELD_BG_COLOR, this ); } else { g.drawImage( ballImage[col], rx, ry, FIELD_BG_COLOR, this ); } } } } } } void drawScore(Graphics g) { // Score in text int rx = scoreX - SKIPX; int ry = scoreY; int rwidth = SCORE_DX; int rheight = SCORE_DY; g.setColor(SCORE_BG_COLOR); g.fillRect(rx, ry, rwidth, rheight); g.setColor(Color.black); g.drawRect(rx, ry, rwidth, rheight); g.setFont(scoreFont); g.setColor(SCORE_COLOR); g.drawString("" + score, rx + 4, ry + 14); // Draw score bar rx = scoreBarX; rwidth = BAR_DX; ry = scoreBarY1 - 4; scoreTop = 4; rheight = scoreTop; if (score != 0 && record != 0) { if (score < record) scoreTop = ((scoreBarY1 - scoreBarY0) * score) / record; else scoreTop = scoreBarY1 - scoreBarY0; ry = scoreBarY1 - scoreTop; rheight = scoreTop; } g.setColor(SCORE_BAR_COLOR); g.fillRect(rx, ry, rwidth, rheight); } void drawScore() { if (offscreenImage == null) { repaint(); } else { drawScore(offscreenGraphics); drawOffscreen(); } } void drawRecord(Graphics g) { int rx = recordX; int ry = recordY; int rwidth = SCORE_DX; int rheight = SCORE_DY; g.setColor(SCORE_BG_COLOR); g.fillRect(rx, ry, rwidth, rheight); g.setColor(Color.black); g.drawRect(rx, ry, rwidth, rheight); g.setFont(scoreFont); g.setColor(RECORD_COLOR); g.drawString("" + record, rx + 4, ry + 14); // Draw record bar rx = recordBarX; rwidth = BAR_DX; ry = recordBarY0; recordTop = recordBarY1 - recordBarY0; rheight = recordTop; if (score > record && score > 0) { recordTop = ((recordBarY1 - recordBarY0) * record) / score; ry = recordBarY1 - recordTop; rheight = recordTop; } g.setColor(RECORD_BAR_COLOR); g.fillRect(rx, ry, rwidth, rheight); if (recordTop < recordBarY1 - recordBarY0) { g.setColor(BG_COLOR); g.fillRect( rx, recordBarY0, rwidth, (recordBarY1 - recordBarY0) - recordTop ); } } void drawRecord() { if (offscreenImage == null) { repaint(); } else { drawRecord(offscreenGraphics); drawOffscreen(); } } void draw3NewBalls(Graphics g) { int rx = balls3X + GRIDX + BALL_DX; int ry = balls3Y; int rwidth = DX - 2*BALL_DX; int rheight = DY - 2*BALL_DY; imageError = (!tracker.checkAll() || tracker.isErrorAny()); for (int i = 0; i < 3; i++) { if (imageError) { g.setColor(colors[newColors[i]]); g.fillOval(rx, ry, rwidth, rheight); g.setColor(Color.black); g.drawOval(rx, ry, rwidth, rheight); } else { g.drawImage( nextBallImage[newColors[i]], rx, ry, BG_COLOR, this ); } rx += DX + GRIDX; } } void drawGameOver(Graphics g) { int rx = fieldX + 3*(GRIDX + DX) + DX/3; int ry = fieldY + 4*(GRIDY + DY) + DY/3; int rwidth = (GRIDX + DX)*3; int rheight = GRIDY + DY; g.setColor(Color.white); g.fillRect(rx, ry, rwidth, rheight); g.setColor(Color.black); g.drawRect(rx, ry, rwidth, rheight); g.setColor(Color.red); g.setFont(scoreFont); g.drawString("Game over", rx + 10, ry + 20); } void drawGameOver() { drawGameOver(offscreenGraphics); drawOffscreen(); } void drawNoWay(Graphics g) { int rx = fieldX + 3*(GRIDX + DX) + DX/3; int ry = fieldY + 4*(GRIDY + DY) + DY/3; int rwidth = (GRIDX + DX)*3; int rheight = GRIDY + DY; g.setColor(Color.white); g.fillRect(rx, ry, rwidth, rheight); g.setColor(Color.black); g.drawRect(rx, ry, rwidth, rheight); g.setColor(Color.red); g.setFont(scoreFont); g.drawString("No way...", rx + 10, ry + 20); } void drawNoWay() { drawNoWay(offscreenGraphics); drawOffscreen(); try { Thread.sleep(1000); } catch (InterruptedException e) {}; drawInOffscreen(); repaint(); } void nextBalls() { newColors[0] = randomN(NUM_COLORS); newColors[1] = randomN(NUM_COLORS); newColors[2] = randomN(NUM_COLORS); } int randomN(int n) { return (int)(Math.random() * n); } synchronized void throwBalls() { if (numFree > 0) throwBall(newColors[0]); if (numFree > 0) throwBall(newColors[1]); if (numFree > 0) throwBall(newColors[2]); ballActive = false; nextBalls(); draw3NewBalls(); // Check end of game if (numFree <= 0) gameFinished(); System.gc(); } void throwBall(int color) { int x, y; boolean found; if (numFree <= 0) return; x = randomN(WIDTH); y = randomN(HEIGHT); found = false; while (!found) { if (field[x][y] == EMPTY) { field[x][y] = (byte) color; numBalls++; numFree--; found = true; } else { x = randomN(WIDTH); y = randomN(HEIGHT); } } drawCell(x, y); userMove = false; if (checkRows(x, y) > 0) { removeRows(x, y); } } void drawCell(int x, int y) { if (offscreenImage == null) { repaintCell(x, y); } else { drawCell(x, y, offscreenGraphics); //... drawOffscreen(); Graphics g = getGraphics(); g.clipRect(cellX(x), cellY(y), DX, DY); g.drawImage(offscreenImage, 0, 0, this); //... g.clipRect(0, 0, offscreenRect.width, offscreenRect.height); } } void repaintCell(int x, int y) { repaint(cellX(x), cellY(y), DX, DY); } void drawCell(int x, int y, Graphics g) { int col = field[x][y]; int rx, ry, rwidth, rheight; imageError = (!tracker.checkAll() || tracker.isErrorAny()); if (col != EMPTY) { col %= (NUM_COLORS + 1); rx = fieldX + GRIDX + x * (DX + GRIDX) + BALL_DX; rwidth = DX - 2 * BALL_DX; ry = fieldY + GRIDY + y * (DY + GRIDY) + BALL_DY; rheight = DY - 2 * BALL_DY; if ( ballActive && x == activeBallX && y == activeBallY ) { // Erase a cell g.setColor(FIELD_BG_COLOR); g.fillRect(rx, ry, rwidth, rheight); if (!activeBallBig) { rx += ACTIVE_BALL_DX; ry += ACTIVE_BALL_DY; rwidth -= 2*ACTIVE_BALL_DX; rheight -= 2*ACTIVE_BALL_DY; } } if (imageError) { g.setColor(colors[col]); g.fillOval(rx, ry, rwidth, rheight); g.setColor(Color.black); g.drawOval(rx, ry, rwidth, rheight); } else { if ( ballActive && !activeBallBig && x == activeBallX && y == activeBallY ) { g.drawImage( smallBallImage[col], rx, ry, FIELD_BG_COLOR, this ); } else { g.drawImage( ballImage[col], rx, ry, FIELD_BG_COLOR, this ); } } } else { rx = fieldX + GRIDX + x * (DX + GRIDX); rwidth = DX; ry = fieldY + GRIDY + y * (DY + GRIDY); rheight = DY; g.setColor(FIELD_BG_COLOR); g.fillRect(rx, ry, rwidth, rheight); } } int checkRows(int x, int y) { // Returns number of completed rows int x0, x1, y0, y1; int col; int numCompletedRows = 0; col = field[x][y]; // Top-bottom y0 = y; while (y0 > 0 && field[x][y0-1] == col) y0--; y1 = y; while (y1 < HEIGHT-1 && field[x][y1+1] == col) y1++; topBottom[0][0] = (byte) x; topBottom[0][1] = (byte) y0; topBottom[1][0] = (byte) x; topBottom[1][1] = (byte) y1; topBottomLen = y1 + 1 - y0; if (topBottomLen >= 5) numCompletedRows++; // Left-right x0 = x; while (x0 > 0 && field[x0-1][y] == (byte) col) x0--; x1 = x; while (x1 < WIDTH-1 && field[x1+1][y] == (byte) col) x1++; leftRight[0][0] = (byte) x0; leftRight[0][1] = (byte) y; leftRight[1][0] = (byte) x1; leftRight[1][1] = (byte) y; leftRightLen = x1 + 1 - x0; if (leftRightLen >= 5) numCompletedRows++; // Left diagonal x0 = x; y0 = y; while (x0 > 0 && y0 > 0 && field[x0-1][y0-1] == (byte) col) { x0--; y0--; } x1 = x; y1 = y; while ( x1 < WIDTH-1 && y1 < HEIGHT-1 && field[x1+1][y1+1] == (byte) col ) { x1++; y1++; } leftDiag[0][0] = (byte) x0; leftDiag[0][1] = (byte) y0; leftDiag[1][0] = (byte) x1; leftDiag[1][1] = (byte) y1; leftDiagLen = x1 + 1 - x0; if (leftDiagLen >= 5) numCompletedRows++; // Right diagonal x0 = x; y0 = y; while (x0 > 0 && y0 < HEIGHT-1 && field[x0-1][y0+1] == (byte) col) { x0--; y0++; } x1 = x; y1 = y; while (x1 < WIDTH-1 && y1 > 0 && field[x1+1][y1-1] == (byte) col) { x1++; y1--; } rightDiag[0][0] = (byte) x0; rightDiag[0][1] = (byte) y0; rightDiag[1][0] = (byte) x1; rightDiag[1][1] = (byte) y1; rightDiagLen = x1 + 1 - x0; if (rightDiagLen >= 5) numCompletedRows++; return numCompletedRows; } synchronized void removeRows(int x, int y) { int i, j; boolean removed = false; int ballsRemoved = 0; int linesRemoved = 0; // Top-bottom if (topBottomLen >= 5) { i = topBottom[0][0]; j = topBottom[0][1]; while (j <= topBottom[1][1]) { if (!removed || i != x || j != y) { if (i == x && j == y) removed = true; field[i][j] = (byte) EMPTY; numBalls--; numFree++; ballsRemoved++; drawRemovedCell(i, j); try { Thread.sleep(DT); } catch (InterruptedException e) {} if (userMove) { score += BONUS; if (ballsRemoved > 5) score += BONUS2; if (linesRemoved > 0) score += BONUS3; } } j++; } linesRemoved++; } // Left-right if (leftRightLen >= 5) { i = leftRight[0][0]; j = leftRight[0][1]; while (i <= leftRight[1][0]) { if (!removed || i != x || j != y) { if (i == x && j == y) removed = true; field[i][j] = (byte) EMPTY; numBalls--; numFree++; ballsRemoved++; drawRemovedCell(i, j); try { Thread.sleep(DT); } catch (InterruptedException e) {} if (userMove) { score += BONUS; if (ballsRemoved > 5) score += BONUS2; if (linesRemoved > 0) score += BONUS3; } } i++; } linesRemoved++; } // Left diagonal if (leftDiagLen >= 5) { i = leftDiag[0][0]; j = leftDiag[0][1]; while (i <= leftDiag[1][0]) { if (!removed || i != x || j != y) { if (i == x && j == y) removed = true; field[i][j] = (byte) EMPTY; numBalls--; numFree++; ballsRemoved++; drawRemovedCell(i, j); try { Thread.sleep(DT); } catch (InterruptedException e) {} if (userMove) { score += BONUS; if (ballsRemoved > 5) score += BONUS2; if (linesRemoved > 0) score += BONUS3; } } i++; j++; } linesRemoved++; } // Right diagonal if (rightDiagLen >= 5) { i = rightDiag[0][0]; j = rightDiag[0][1]; while (i <= rightDiag[1][0]) { if (!removed || i != x || j != y) { if (i == x && j == y) removed = true; field[i][j] = (byte) EMPTY; numBalls--; numFree++; ballsRemoved++; drawRemovedCell(i, j); try { Thread.sleep(DT); } catch (InterruptedException e) {} if (userMove) { score += BONUS; if (ballsRemoved > 5) score += BONUS2; if (linesRemoved > 0) score += BONUS3; } } i++; j--; } linesRemoved++; } while (movingThreadRunning > 0) { try { Thread.sleep(DT/10); } catch (InterruptedException e) {} } if (userMove) { if (score > record) { drawRecord(offscreenGraphics); } drawScore(offscreenGraphics); } // try { // Thread.sleep(5*DT); // } catch (InterruptedException e) {} repaint(); } void draw3NewBalls() { if (offscreenGraphics != null) { draw3NewBalls(offscreenGraphics); drawOffscreen(); } else { // System.out.println("draw3NewBalls: offscreenGraphics == null"); } } int cellX(int i) { return (fieldX + GRIDX + i * (DX + GRIDX)); } int cellY(int j) { return (fieldY + GRIDY + j * (DX + GRIDX)); } synchronized void drawMovingBall(int x, int y, int colorIndex) { final int xx = x; final int yy = y; final int cIdx = colorIndex; Runnable moveBall = new Runnable() { public void run() { movingThreadRunning++; byte prevColor = field[xx][yy]; field[xx][yy] = (byte) cIdx; drawCell(xx, yy); try { Thread.sleep(DT); } catch (InterruptedException e) {} field[xx][yy] = prevColor; drawCell(xx, yy, offscreenGraphics); movingThreadRunning--; } }; Thread movingBallThread = new Thread(moveBall, "Moving Ball"); movingBallThread.start(); } void drawRemovedCell(int x, int y) { drawMovingBall(x, y, NUM_COLORS); } void onNewGame() { if (record < score) { record = score; } putInRecordTable(); initGame(); drawInOffscreen(); repaint(); } void onSkip() { ballActive = false; activeBallBig = true; drawCell(activeBallX, activeBallY); if (numFree <= 0) { gameFinished(); } else { while (movingThreadRunning > 0) { try { Thread.sleep(DT/10); } catch (InterruptedException e) {} } throwBalls(); } bonusMove = false; } void showRecords(Graphics g) { int rx = fieldX + DX/3; int ry = fieldY + DY/3; int rwidth = (GRIDX + DX)*WIDTH + GRIDX - (DX*2)/3; int rheight = SCORE_DY*RECORD_TABLE_LEN + SKIPY; g.setColor(Color.white); g.fillRect(rx, ry, rwidth, rheight); g.setColor(Color.black); g.drawRect(rx, ry, rwidth, rheight); g.setFont(scoreFont); int x0 = rx + SKIPX; int x1 = x0 + SCORE_DX; int y = ry + 20; for (int i = 0; i < recordTableLen; i++) { g.drawString( "" + records[i], x0, y ); g.drawString( "" + recordNames[i], x1, y ); y += SCORE_DY; } } void onRecordTable() { showRecordTable = true; showRecords(offscreenGraphics); repaint(); } void onQuit() { if (parentFrame != null) { // Not an applet putInRecordTable(); saveRecordTable(); } System.exit(0); } void initGame() { numBalls = 0; numFree = WIDTH * HEIGHT; score = 0; scoreWrittenInRecordTable = false; scoreTop = 0; recordTop = 0; ballActive = false; activeBallBig = true; gameOver = false; for (int x = 0; x < WIDTH; x++) { for (int y = 0; y < HEIGHT; y++) { field[x][y] = (byte) EMPTY; } } nextBalls(); // 3 First balls throwBalls(); } void onMouseDown(int h, int v) { int x = (h - fieldX - GRIDX) / (GRIDX + DX); int y = (v - fieldY - GRIDY) / (GRIDY + DY); if (x < 0 || x >= WIDTH) return; if (y < 0 || y >= HEIGHT) return; if (ballActive) { if (activeBallX == x && activeBallY == y) { // Switch off active ball ballActive = false; drawCell(x, y); } else if (field[x][y] != (byte) EMPTY) { // Change active ball ballActive = false; // Wait until active thread finishes while (activeBallThreadRunning) { try { Thread.sleep(DT/10); } catch (InterruptedException e) {} } drawCell(activeBallX, activeBallY); activeBallX = x; activeBallY = y; ballActive = true; startActiveBall(); } else if (correctMove(x, y)) { userMove = true; makeMove(x, y); userMove = false; } else { // Incorrect move... Toolkit.getDefaultToolkit().beep(); drawNoWay(); } } else { if (field[x][y] == (byte) EMPTY) return; activeBallX = x; activeBallY = y; ballActive = true; startActiveBall(); } } boolean correctMove(int x, int y) { if (!( x >= 0 && x < WIDTH && field[x][y] == (byte) EMPTY && y >= 0 && y < HEIGHT && field[x][y] == (byte) EMPTY )) return false; // Find a way return findPath(activeBallX, activeBallY, x, y); } boolean findPath(int x0, int y0, int x1, int y1) { int x, y; int n0, n1; int pLen, l; boolean found = false; int MAXLEN = WIDTH*HEIGHT + 1; for (x = 0; x < WIDTH; x++) { for (y = 0; y < HEIGHT; y++) { paths[x][y] = (-1); } } paths[x0][y0] = 0; n0 = (-1); n1 = 0; while (!found && n1 > n0) { n0 = n1; for (x = 0; !found && x < WIDTH; x++) { for (y = 0; !found && y < HEIGHT; y++) { if (paths[x][y] >= 0 || field[x][y] != (byte) EMPTY) continue; // Look to neighbours int xmin = 0, ymin = 0; int lmin = MAXLEN; if (x+1 < WIDTH && (l = paths[x+1][y]) >= 0) { if (l < lmin) { xmin = x+1; ymin = y; lmin = l; } } if (y+1 < HEIGHT && (l = paths[x][y+1]) >= 0) { if (l < lmin) { xmin = x; ymin = y+1; lmin = l; } } if (x-1 >= 0 && (l = paths[x-1][y]) >= 0) { if (l < lmin) { xmin = x-1; ymin = y; lmin = l; } } if (y-1 >= 0 && (l = paths[x][y-1]) >= 0) { if (l < lmin) { xmin = x; ymin = y-1; lmin = l; } } if (lmin < MAXLEN) { n1++; paths[x][y] = lmin + 1; } if (y == y1 && paths[x1][y1] >= 0) { found = true; } } } } // System.out.println("Path found."); if (!found) return false; // Obtain a path pathLength = paths[x1][y1]; pLen = pathLength; x = x1; y = y1; while (pLen > 0) { // Look to neighbours. // Find path of minimal length int xmin = 0, ymin = 0; int lmin = WIDTH*HEIGHT + 1; if (x+1 < WIDTH && (l = paths[x+1][y]) >= 0) { if (l < lmin) { xmin = x+1; ymin = y; lmin = l; } } if (y+1 < HEIGHT && (l = paths[x][y+1]) >= 0) { if (l < lmin) { xmin = x; ymin = y+1; lmin = l; } } if (x-1 >= 0 && (l = paths[x-1][y]) >= 0) { if (l < lmin) { xmin = x-1; ymin = y; lmin = l; } } if (y-1 >= 0 && (l = paths[x][y-1]) >= 0) { if (l < lmin) { xmin = x; ymin = y-1; lmin = l; } } pLen--; x = xmin; y = ymin; path[pLen][0] = (byte) x; path[pLen][1] = (byte) y; // System.out.print(" ("+x+", "+y+") "); } // System.out.println(""); return true; } void startActiveBall() { Runnable blinkingBall = new Runnable() { public void run() { activeBallThreadRunning = true; while (ballActive) { try { Thread.sleep(600); } catch (InterruptedException e) { } if (ballActive) { activeBallBig = !activeBallBig; drawCell(activeBallX, activeBallY); } } activeBallThreadRunning = false; } }; Thread blinkingThread = new Thread(blinkingBall, "Blinking Thread"); blinkingThread.start(); } void moveBall() { int x = activeBallX; int y = activeBallY; int c = field[x][y]; ballActive = false; activeBallBig = true; field[x][y] = (byte) EMPTY; while (activeBallThreadRunning) { try { Thread.sleep(DT/10); } catch (InterruptedException e) {} } //... drawCell(x, y, offscreenGraphics); drawCell(x, y); for (int i = 1; i < pathLength; i++) { x = path[i][0]; y = path[i][1]; drawMovingBall(x, y, c); try { Thread.sleep(DT/2); } catch (InterruptedException e) {} while (movingThreadRunning > 0) { try { Thread.sleep(DT/10); } catch (InterruptedException e) {} } drawCell(x, y); } } void makeMove(int x, int y) { int col = field[activeBallX][activeBallY]; moveBall(); field[x][y] = (byte) col; drawCell(x, y); afterMove(x, y); if (!bonusMove) { if (numFree <= 0) { gameFinished(); } else { throwBalls(); } } } void afterMove(int x, int y) { int numRows = checkRows(x, y); if (numRows > 0) { bonusMove = true; Toolkit.getDefaultToolkit().beep(); drawCell(x, y); removeRows(x, y); } else { bonusMove = false; } } void gameFinished() { ballActive = false; gameOver = true; putInRecordTable(); drawGameOver(); } public void start() {} public void stop() {} void loadRecordTable() { FileReader fr; try { fr = new FileReader(RECORD_FILE); } catch (FileNotFoundException e) { System.out.println( "Cannot open record file: " + e ); return; } BufferedReader br = new BufferedReader(fr); String line; try { while ( recordTableLen < RECORD_TABLE_LEN && (line = br.readLine()) != null ) { int l = 0; while ( l < line.length() && Character.isDigit(line.charAt(l)) ) l++; if (l > 0) { records[recordTableLen] = Integer.valueOf( line.substring(0, l) ).intValue(); recordNames[recordTableLen] = line.substring(l+1); recordTableLen++; } } br.close(); fr.close(); if (recordTableLen > 0 && records[0] > record) record = records[0]; } catch (IOException e) { System.out.println("Error reading record file: " + e); } } void saveRecordTable() { if (recordTableLen <= 0) return; FileWriter fw; try { fw = new FileWriter(RECORD_FILE); } catch (IOException e) { System.out.println( "Cannot open record file for writing: " + e ); return; } try { for (int i = 0; i < recordTableLen; i++) { String line = "" + records[i] + " " + recordNames[i] + "\n"; fw.write(line, 0, line.length()); } fw.close(); } catch (IOException e) { System.out.println("Error writing record file: " + e); } } class NameDialog extends Dialog implements ActionListener { Button ok; Button cancel; TextField name; public NameDialog(Frame f) { super(f, true); setLayout(null); setFont(scoreFont); setBackground(Color.lightGray); setForeground(Color.black); Label l = new Label("Congratulations with high result!"); int y = SKIPY + SCORE_DY; l.setBounds(SKIPX, y, 240, SCORE_DY); add(l); y += SCORE_DY + SKIPY; l = new Label("Enter your name:"); l.setBounds(SKIPX, y, 100, SCORE_DY); add(l); name = new TextField(); name.setBounds(SKIPX + 104, y, 136, SCORE_DY); add(name); y += SCORE_DY + SKIPY; ok = new Button("OK"); cancel = new Button("Cancel"); ok.setBounds(SKIPX + 240 - 60, y, 60, SCORE_DY); cancel.setBounds(240 - 60 - 80, y, 80, SCORE_DY); add(ok); add(cancel); y += SCORE_DY + SKIPY; name.addActionListener(this); ok.addActionListener(this); cancel.addActionListener(this); userNameOK = false; name.requestFocus(); // setSize(240 + 2*SKIPX, y + SKIPY); setBounds(60, 60, 240 + 2*SKIPX, y + SKIPY); } public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (source == name || source == ok) { userName = name.getText(); userNameOK = true; } setVisible(false); } } void putInRecordTable() { if (parentFrame == null) return; // Work from WEB browser if (score <= 0 || scoreWrittenInRecordTable) return; scoreWrittenInRecordTable = true; int n = 0; while ( n < recordTableLen && score <= records[n] ) n++; if (n >= RECORD_TABLE_LEN) return; NameDialog nd = new NameDialog(parentFrame); if (userNameOK) nd.name.setText(userName); nd.setVisible(true); if (userNameOK) { // Insert new record int i = recordTableLen; if (i >= RECORD_TABLE_LEN) i--; while (i > n) { records[i] = records[i-1]; recordNames[i] = recordNames[i-1]; i--; } if (recordTableLen < RECORD_TABLE_LEN) recordTableLen++; records[n] = score; recordNames[n] = userName; } } public boolean accept(File f, String fileName) { return ( fileName.length() >= 4 && fileName.substring(fileName.length() - 4, 4).equals(".dat") ); } boolean onLoadGame() { if (parentFrame == null) return false; FileDialog fd = new FileDialog( parentFrame, "Load Game", FileDialog.LOAD ); if (directory != null) fd.setDirectory(directory); if (fileName != null) fd.setFile(fileName); fd.setFilenameFilter(this); fd.setVisible(true); //... System.out.println("Directory = " + fd.getDirectory()); //... System.out.println("File name = " + fd.getFile()); if (fd.getDirectory() == null || fd.getFile() == null) return false; directory = fd.getDirectory(); fileName = fd.getFile(); String path = directory + fileName; //... BufferedReader f; DataInputStream f; try { //... f = new BufferedReader( //... new FileReader(path) //... ); f = new DataInputStream( new FileInputStream(path) ); } catch (FileNotFoundException e) { System.out.println("Cannot open file for reading: " + e); fileName = null; return false; } initGame(); try { scoreTop = f.readInt(); recordTop = f.readInt(); WIDTH = f.readInt(); HEIGHT = f.readInt(); //... System.out.println("WIDTH=" + WIDTH + ", HEIGHT=" + HEIGHT); for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { field[x][y] = f.readByte(); } } numBalls = f.readInt(); numFree = f.readInt(); newColors[0] = f.readInt(); newColors[1] = f.readInt(); newColors[2] = f.readInt(); score = f.readInt(); record = f.readInt(); f.close(); } catch (IOException e) { System.out.println("Read error: " + e); } drawInOffscreen(); repaint(); return true; } boolean onSaveGame() { if (parentFrame == null) return false; FileDialog fd = new FileDialog( parentFrame, "Save Game", FileDialog.SAVE ); if (directory != null) fd.setDirectory(directory); if (fileName != null) fd.setFile(fileName); fd.setFilenameFilter(this); fd.setVisible(true); //... System.out.println("" + fd.getDirectory()); //... System.out.println("" + fd.getFile()); if (fd.getDirectory() == null || fd.getFile() == null) return false; directory = fd.getDirectory(); fileName = fd.getFile(); String path = directory + fileName; //... BufferedWriter f; DataOutputStream f; try { //... f = new BufferedWriter( //... new FileWriter(path) //... ); f = new DataOutputStream( new FileOutputStream(path) ); } catch (IOException e) { System.out.println("Cannot open file for writing: " + e); return false; } try { f.writeInt(scoreTop); f.writeInt(recordTop); f.writeInt(WIDTH); f.writeInt(HEIGHT); for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { f.writeByte(field[x][y]); } } f.writeInt(numBalls); f.writeInt(numFree); f.writeInt(newColors[0]); f.writeInt(newColors[1]); f.writeInt(newColors[2]); f.writeInt(score); f.writeInt(record); f.close(); } catch (IOException e) { System.out.println("Write error: " + e); return false; } return true; } class FieldBgFilter extends RGBImageFilter { public FieldBgFilter() { canFilterIndexColorModel = true; } // Replace grey by FIELD_BG_COLOR public int filterRGB(int x, int y, int rgb) { int red = ((rgb & 0xff0000) >> 16); int green = ((rgb & 0x00ff00) >> 8); int blue = (rgb & 0x0000ff); if (red == 255 && green == 255 && blue == 255) { // System.out.println("red="+red+" green="+green+" blue="+blue); return ( (rgb & 0xff000000) | (FIELD_BG_COLOR.getRed() << 16) | (FIELD_BG_COLOR.getGreen() << 8) | FIELD_BG_COLOR.getBlue() ); } else { return rgb; } } } class BgFilter extends RGBImageFilter { public BgFilter() { canFilterIndexColorModel = true; } // Replace grey by BG_COLOR public int filterRGB(int x, int y, int rgb) { int red = ((rgb & 0xff0000) >> 16); int green = ((rgb & 0x00ff00) >> 8); int blue = (rgb & 0x0000ff); if (red == 255 && green == 255 && blue == 255) { return ( (rgb & 0xff000000) | (BG_COLOR.getRed() << 16) | (BG_COLOR.getGreen() << 8) | BG_COLOR.getBlue() ); } else { return rgb; } } } }