/** * Game "Python" * Written by V.Borisenko, vvb@mech.math.msu.su, Moscow State Univ., 1998 * * * */ import java.awt.*; import java.awt.event.*; import java.applet.Applet; import java.lang.Math; import java.util.Date; import java.util.Vector; public class Python extends Applet implements Runnable { static final int WIDTH = 39; static final int HEIGHT = 20; static final int MAX_PYTHON_LENGTH = WIDTH * HEIGHT; static final int PYTHON_INITIAL_LENGTH = 4; static final int MAX_NUM_MICE = 16; byte field[][] = new byte[WIDTH][HEIGHT]; Vector python = new Vector(); Mouse[] mice = new Mouse[MAX_NUM_MICE]; int numMice = 0; // Possible field elements static final byte EMPTY = (byte) (-1); static final byte PYTHON = (byte) 127; // Otherwise this is the mouse index // Timing (millisec) static final long MOVE_DT = 100; static final long GROW_DT = 1000; static final long MOUSE_DT = 5000; // Average mouse life static final long NEW_MOUSE_DT = 3000; // Time between mice birth long startTime = 0; long curTime = 0; long timePassed = 0; long growTime = 0; // Thread Thread gameThread = null; boolean terminateGame = false; boolean pause = false; // Score int score = 0; int miceDevoured = 0; static final int LENGTH_BONUS = 1; static final int MOUSE_BONUS = 10; // Labels Label scoreLabel; Label miceDevouredLabel; Label pythonLengthLabel; Label timeLabel; // Buttons Button startButton; Button stopButton; Button pauseButton; // Layout constants static final int LEFT_MARGIN = 5; static final int TOP_MARGIN = 5; static final int DX4 = 4; static final int DX2 = 2 * DX4; static final int DX = 4 * DX4; static final int DY4 = 4; static final int DY2 = 2 * DY4; static final int DY = 4 * DY4; static final int VERT_SKIP = 5; static final int HOR_SKIP = 4; static final int BUTTON_HEIGHT = 22; // Directions static final int DIR_RIGHT = 0; static final int DIR_UP = 1; static final int DIR_LEFT = 2; static final int DIR_DOWN = 3; static final int DIR_UNDEFINED = (-1); int pythonDirection = DIR_RIGHT; int newDirection = DIR_RIGHT; // Fonts Font labelFont = new Font("TimesRoman", Font.BOLD, 14); Font gameOverFont = new Font("TimesRoman", Font.BOLD, 18); // Colors Color pythonColor = Color.blue; Color mouseColor = Color.darkGray; Color labelColor = Color.black; Color scoreColor = Color.blue; Color timeColor = Color.blue; Color gameOverColor = Color.red; String gameOverString = "Game over!"; boolean gameOver = false; // Keystroke queue Queue keyQueue = new Queue(); public static void main(String[] args) { class MyFrame extends Frame { MyFrame(String headline) { super(headline); } public boolean action(Event e, Object arg) { if (e.target instanceof MenuItem) { // We have only one "Quit" menu item... System.exit(0); } return false; } public boolean handleEvent(Event e) { if (e.id == Event.WINDOW_DESTROY) { System.exit(0); } return super.handleEvent(e); } }; Frame f = new MyFrame("Python"); f.setFont(new Font("Helvetica", Font.PLAIN, 14)); MenuBar mb = new MenuBar(); f.setMenuBar(mb); Menu fileMenu = new Menu("File"); fileMenu.add(new MenuItem("Quit")); mb.add(fileMenu); f.resize( 2*LEFT_MARGIN + WIDTH * DX + 16, // ??? 2*TOP_MARGIN + HEIGHT * DY + VERT_SKIP + BUTTON_HEIGHT + 24 // ??? + 30 // Menu bar width ); Python p = new Python(); f.add("Center", p); p.init(); p.start(); f.show(); } public void Python() { initialize(false); } public void initialize(boolean putPython) { // Erase the field for (int x = 0; x < WIDTH; x++) { for (int y = 0; y < HEIGHT; y++) { field[x][y] = EMPTY; } } // Erase mice for (int m = 0; m < MAX_NUM_MICE; m++) { mice[m] = null; } numMice = 0; // Initialize the python python.removeAllElements(); if (putPython) { int y = 4 + (int)(Math.random() * (HEIGHT - 8)); for (int i = 0; i < PYTHON_INITIAL_LENGTH; i++) { python.addElement( new Link(4+i, y) ); field[4+i][y] = PYTHON; } } score = 0; showScore(); miceDevoured = 0; showMiceDevoured(); showPythonLength(); timeLabel.setText("0"); startButton.enable(); stopButton.disable(); pauseButton.disable(); gameOver = false; pythonDirection = DIR_RIGHT; newDirection = DIR_RIGHT; keyQueue.init(); gameOverString = "Game over!"; if (putPython) repaint(); } public void init() { setLayout(null); resize( 2*LEFT_MARGIN + WIDTH * DX, 2*TOP_MARGIN + HEIGHT * DY + VERT_SKIP + BUTTON_HEIGHT ); setBackground(Color.lightGray); setForeground(Color.blue); setFont(labelFont); // Window elements int buttonY0 = TOP_MARGIN + HEIGHT * DY + VERT_SKIP; int x = LEFT_MARGIN; Label sLabel = new Label("Score:", Label.LEFT); sLabel.setFont(labelFont); sLabel.setForeground(labelColor); final int sLabelWidth = sLabel.getFontMetrics(labelFont).stringWidth("Score:") + 4; add(sLabel); sLabel.reshape(x, buttonY0, sLabelWidth, BUTTON_HEIGHT); x += sLabelWidth + HOR_SKIP; scoreLabel = new Label("0", Label.LEFT); scoreLabel.setFont(labelFont); scoreLabel.setForeground(scoreColor); final int scoreWidth = scoreLabel.getFontMetrics(labelFont).stringWidth("0000") + 4; add(scoreLabel); scoreLabel.reshape(x, buttonY0, scoreWidth, BUTTON_HEIGHT); x += scoreWidth + HOR_SKIP; Label mLabel = new Label("Mice devoured:", Label.LEFT); mLabel.setFont(labelFont); mLabel.setForeground(labelColor); final int mLabelWidth = mLabel.getFontMetrics(labelFont).stringWidth("Mice devoured:") + 4; add(mLabel); mLabel.reshape(x, buttonY0, mLabelWidth, BUTTON_HEIGHT); x += mLabelWidth + HOR_SKIP; miceDevouredLabel = new Label("0", Label.LEFT); miceDevouredLabel.setFont(labelFont); miceDevouredLabel.setForeground(scoreColor); final int miceDevouredLabelWidth = miceDevouredLabel.getFontMetrics(labelFont).stringWidth("000") + 4; add(miceDevouredLabel); miceDevouredLabel.reshape( x, buttonY0, miceDevouredLabelWidth, BUTTON_HEIGHT ); x += miceDevouredLabelWidth + HOR_SKIP; Label pLength = new Label("Python length:", Label.LEFT); pLength.setFont(labelFont); pLength.setForeground(labelColor); final int pLengthWidth = pLength.getFontMetrics(labelFont).stringWidth("Python length:") + 4; add(pLength); pLength.reshape(x, buttonY0, pLengthWidth, BUTTON_HEIGHT); x += pLengthWidth + HOR_SKIP; pythonLengthLabel = new Label("4", Label.LEFT); pythonLengthLabel.setFont(labelFont); pythonLengthLabel.setForeground(scoreColor); final int pythonLengthLabelWidth = pythonLengthLabel.getFontMetrics(labelFont).stringWidth("000") + 4; add(pythonLengthLabel); pythonLengthLabel.reshape(x, buttonY0, pythonLengthLabelWidth, BUTTON_HEIGHT); x += pythonLengthLabelWidth + HOR_SKIP; Label tLabel = new Label("Time passed:", Label.LEFT); tLabel.setFont(labelFont); tLabel.setForeground(labelColor); final int tLabelWidth = tLabel.getFontMetrics(labelFont).stringWidth("Time passed:") + 4; add(tLabel); tLabel.reshape(x, buttonY0, tLabelWidth, BUTTON_HEIGHT); x += tLabelWidth + HOR_SKIP; timeLabel = new Label("0", Label.LEFT); timeLabel.setFont(labelFont); timeLabel.setForeground(timeColor); final int timeLabelWidth = timeLabel.getFontMetrics(labelFont).stringWidth("0000") + 4; add(timeLabel); timeLabel.reshape(x, buttonY0, timeLabelWidth, BUTTON_HEIGHT); x += timeLabelWidth + HOR_SKIP; int buttonX1 = LEFT_MARGIN + WIDTH * DX; pauseButton = new Button("Pause"); pauseButton.setFont(labelFont); pauseButton.setForeground(labelColor); final int pauseWidth = pauseButton.getFontMetrics(labelFont).stringWidth("Pause") + 8; add(pauseButton); x = buttonX1 - pauseWidth; pauseButton.reshape(x, buttonY0, pauseWidth, BUTTON_HEIGHT); x -= HOR_SKIP; stopButton = new Button("Stop"); stopButton.setFont(labelFont); stopButton.setForeground(labelColor); final int stopWidth = stopButton.getFontMetrics(labelFont).stringWidth("Stop") + 8; add(stopButton); x -= stopWidth; stopButton.reshape(x, buttonY0, stopWidth, BUTTON_HEIGHT); x -= HOR_SKIP; startButton = new Button("Start"); startButton.setFont(labelFont); startButton.setForeground(labelColor); final int startWidth = startButton.getFontMetrics(labelFont).stringWidth("Start") + 8; add(startButton); x -= startWidth; startButton.reshape(x, buttonY0, startWidth, BUTTON_HEIGHT); x -= HOR_SKIP; initialize(false); requestFocus(); } public boolean keyDown(Event e, int key) { if (e.id == Event.KEY_PRESS) { // System.out.println("Key pressed, code=" + key); } else if (e.id == Event.KEY_ACTION) { if ( key == Event.RIGHT || key == Event.UP || key == Event.LEFT || key == Event.DOWN ) { keyQueue.add(key); } } return false; } public void start() { requestFocus(); } public boolean action(Event e, Object arg) { Object source = e.target; requestFocus(); if (source == startButton) { onStart(); } else if (source == stopButton) { onStop(); } else if (source == pauseButton) { onPause(); } else { return true; } return false; } public void onStart() { if (gameThread != null) { return; } requestFocus(); initialize(true); terminateGame = false; gameThread = new Thread(this, "Game_Thread"); startButton.disable(); stopButton.enable(); pauseButton.enable(); pause = false; gameThread.start(); } public void onStop() { if (gameThread != null) { terminateGame = true; } } public void onPause() { if (gameThread != null) { pause = !pause; } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { // Erase background Rectangle clipRect = g.getClipRect(); //... System.out.println("Clip rect: " + clipRect); g.setColor(Color.lightGray); g.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); g.setColor(pythonColor); g.drawRect( LEFT_MARGIN, TOP_MARGIN, WIDTH * DX, HEIGHT * DY ); drawPython(g); drawMice(g); // Message after end of game if (gameOver) { g.setFont(gameOverFont); g.setColor(gameOverColor); g.drawString( gameOverString, LEFT_MARGIN + (WIDTH / 2 - 10) * DX, TOP_MARGIN + 10 * DY ); } } synchronized void drawPython(Graphics g) { int n = python.size(); if (n < 2) return; Rectangle clipRect = g.getClipRect(); g.setColor(pythonColor); // Tail (triangular shape) Link l0 = (Link) (python.elementAt(0)); Link l1 = (Link) (python.elementAt(1)); int i0 = l0.x; int j0 = l0.y; int i1 = l1.x; int j1 = l1.y; int tx[] = new int[3]; int ty[] = new int[3]; tx[0] = LEFT_MARGIN + i0 * DX + DX / 2; ty[0] = TOP_MARGIN + j0 * DY + DY / 2; if (j0 == j1) { // Horizontal tail tx[1] = tx[0] + 3 * DX4 * (i1 - i0); tx[2] = tx[1]; ty[1] = TOP_MARGIN + j1 * DY + DY4; ty[2] = ty[1] + 2 * DY4; g.fillPolygon(tx, ty, 3); } else { // Vertical tail ty[1] = ty[0] + 3 * DY4 * (j1 - j0); ty[2] = ty[1]; tx[1] = LEFT_MARGIN + i1 * DX + DX4; tx[2] = tx[1] + 2 * DX4; g.fillPolygon(tx, ty, 3); } Rectangle r0 = cellRect(i0, j0); Rectangle r1 = cellRect(i1, j1); for (int l = 1; l < n - 1; l++) { l0 = (Link) (python.elementAt(l)); l1 = (Link) (python.elementAt(l+1)); i0 = l0.x; j0 = l0.y; i1 = l1.x; j1 = l1.y; // Check whether the current link intersects the clip rectangle r0 = r1; r1 = cellRect(i1, j1); if (clipRect.intersects(r0) || clipRect.intersects(r1)) { g.fillRect( LEFT_MARGIN + i0 * DX + DX4, TOP_MARGIN + j0 * DY + DY4, DX2, DY2 ); if (j0 == j1) { // Horizontal link g.fillRect( LEFT_MARGIN + i0 * DX + DX4 + DX2 * (i1 - i0), TOP_MARGIN + j0 * DY + DY4, DX2, DY2 ); } else { // Vertical link g.fillRect( LEFT_MARGIN + i0 * DX + DX4, TOP_MARGIN + j0 * DY + DY4 + DY2 * (j1 - j0), DX2, DY2 ); } } } // Draw head int startAngle = 15; // Direction right if (((i1 + j1) & 1) == 0) startAngle = 30; int arcAngle = 360 - 2 * startAngle; if (i0 == i1 && j0 > j1) { // Up startAngle += 90; } else if (j0 == j1 && i0 > i1) { // Left startAngle += 180; } else if (i0 == i1 && j0 < j1) { // Down startAngle -= 90; } g.fillArc( LEFT_MARGIN + i1 * DX, TOP_MARGIN + j1 * DY, DX, DY, startAngle, arcAngle ); } void drawMice(Graphics g) { Rectangle clipRect = g.getClipRect(); for (int m = 0; m < MAX_NUM_MICE; m++) { Mouse mouse = mice[m]; if ( mouse != null && clipRect.intersects(cellRect(mouse.x, mouse.y)) ) { drawMouse(g, m); } } } void drawMouse(Graphics g, int m) { Mouse mouse = mice[m]; g.setColor(mouseColor); int[] tx = new int[3]; int[] ty = new int[3]; final int x0 = LEFT_MARGIN + mouse.x * DX; final int y0 = LEFT_MARGIN + mouse.y * DY; final int cx = x0 + DX2; final int cy = y0 + DY4; // Mouse body tx[0] = cx; ty[0] = y0; tx[1] = cx - DX4; ty[1] = y0 + 3 * DY4; tx[2] = cx + DX4; ty[2] = ty[1]; g.fillPolygon(tx, ty, 3); // Mouse tail g.drawLine(cx, y0 + 3 * DY4, cx, y0 + DY); g.drawLine(cx, y0 + DY, x0, y0 + DY); // Moustache g.drawLine(cx, cy, cx - DX2, cy - DY4); g.drawLine(cx, cy, cx - DX2, cy); g.drawLine(cx, cy, cx - DX2, cy + DY4); g.drawLine(cx, cy, cx + DX2, cy - DY4); g.drawLine(cx, cy, cx + DX2, cy); g.drawLine(cx, cy, cx + DX2, cy + DY4); } synchronized void movePythonHead() { int n = python.size(); if (n < 1 || terminateGame) return; Link head = (Link) (python.elementAt(n - 1)); int i = head.x; int j = head.y; int i0 = i; int j0 = j; if (!keyQueue.empty()) { // Define new python direction int key = keyQueue.get(); if (key == Event.RIGHT) { newDirection = DIR_RIGHT; } else if (key == Event.UP) { newDirection = DIR_UP; } else if (key == Event.LEFT) { newDirection = DIR_LEFT; } else if (key == Event.DOWN) { newDirection = DIR_DOWN; } } if ( (pythonDirection == DIR_RIGHT && newDirection != DIR_LEFT) || (pythonDirection == DIR_UP && newDirection != DIR_DOWN) || (pythonDirection == DIR_LEFT && newDirection != DIR_RIGHT) || (pythonDirection == DIR_DOWN && newDirection != DIR_UP) ) { pythonDirection = newDirection; } if (pythonDirection == DIR_RIGHT) { if (i < WIDTH - 1) i++; else terminateGame = true; } else if (pythonDirection == DIR_LEFT) { if (i > 0) i--; else terminateGame = true; } else if (pythonDirection == DIR_DOWN) { if (j < HEIGHT - 1) j++; else terminateGame = true; } else if (pythonDirection == DIR_UP) { if (j > 0) j--; else terminateGame = true; } if (terminateGame) gameOverString = "Bumped into the border... Game over!"; // Python cannot bite itself if (!terminateGame && field[i][j] == PYTHON) { terminateGame = true; gameOverString = "Bit itself... Game over!"; } terminateGame = (terminateGame || field[i][j] == PYTHON); if (!terminateGame) { if (field[i][j] != EMPTY) { // This must be a mouse devourMouse(field[i][j]); } python.addElement(new Link(i, j)); field[i][j] = PYTHON; repaintCell(i0, j0); repaintCell(i, j); } } synchronized void movePythonTail() { int n = python.size(); if (n < 2 || terminateGame) return; int tx, ty, t0x, t0y; // Move tail Link tail = (Link) (python.elementAt(0)); t0x = tail.x; t0y = tail.y; try { python.removeElementAt(0); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("" + e); } field[t0x][t0y] = EMPTY; tail = (Link) (python.elementAt(0)); tx = tail.x; ty = tail.y; repaintCell(t0x, t0y); repaintCell(tx, ty); } synchronized void devourMouse(int m) { if (m >= MAX_NUM_MICE || numMice <= 0) return; // For safety Mouse mouse = mice[m]; if (mouse == null) return; // For safety mice[m] = null; numMice--; score += MOUSE_BONUS; showScore(); miceDevoured++; showMiceDevoured(); } synchronized void updateMice() { for (int m = 0; m < MAX_NUM_MICE; m++) { Mouse mouse = mice[m]; if ( mouse != null && mouse.death <= curTime ) { // Remove mouse int x = mouse.x; int y = mouse.y; mice[m] = null; field[x][y] = EMPTY; numMice--; repaintCell(x, y); } } } void showScore() { scoreLabel.setText("" + score); } void showMiceDevoured() { miceDevouredLabel.setText("" + miceDevoured); } void showPythonLength() { pythonLengthLabel.setText("" + python.size()); } void newMouse() { if (numMice >= MAX_NUM_MICE) return; // Look for empty slot int m, x = 0, y = 0; for (m = 0; m < MAX_NUM_MICE && mice[m] != null; m++); if (m >= MAX_NUM_MICE) return; // Define coordinates of new mouse (8 attepmpts) boolean mouseOK = false; for (int i = 0; !mouseOK && i < 8; i++) { x = (int)(WIDTH * Math.random() - 0.001); y = (int)(HEIGHT * Math.random() - 0.001); if (field[x][y] == EMPTY) { mouseOK = true; } } if (!mouseOK) return; mice[m] = new Mouse(x, y, curTime, MOUSE_DT); field[x][y] = (byte) m; numMice++; repaintCell(x, y); } public void run() { System.out.println("Starting game session."); requestFocus(); startTime = (new Date()).getTime(); curTime = startTime; growTime = curTime + GROW_DT; long newMouseTime = curTime + (long)(NEW_MOUSE_DT * 2. * Math.random()); timePassed = 0; gameOver = false; requestFocus(); int step = 0; while (!terminateGame && gameThread != null) { if (pause) continue; try { gameThread.sleep(MOVE_DT); } catch (InterruptedException e) { System.out.println("" + e); } curTime = (new Date()).getTime(); if ((step & 1) == 0) { movePythonHead(); } else { if (curTime - growTime >= GROW_DT) { growTime = curTime; score += LENGTH_BONUS; showScore(); showPythonLength(); } else { movePythonTail(); } } if ((step & 7) == 0) { updateMice(); } // New mouse if (curTime >= newMouseTime) { newMouse(); newMouseTime = curTime + (long)(NEW_MOUSE_DT * 2. * Math.random()); } long tPassed = ((curTime - startTime) / 1000); if (tPassed != timePassed) { timePassed = tPassed; timeLabel.setText("" + timePassed); } step++; } gameThread = null; terminateGame = false; onGameEnd(); } void onGameEnd() { System.out.println(gameOverString); System.out.println( "Game over. Score: " + score + ". Mice devoured: " + miceDevoured + ". Python length: " + python.size() + ". Elapsed time: " + ((curTime - startTime) / 1000) + " sec" ); gameOver = true; startButton.enable(); stopButton.disable(); pauseButton.disable(); repaint(); } void repaintCell(int x, int y) { repaint( MOVE_DT / 3, LEFT_MARGIN + x * DX, TOP_MARGIN + y * DY, DX + 1, DY + 1 ); } Rectangle cellRect(int x, int y) { return new Rectangle( LEFT_MARGIN + x * DX, TOP_MARGIN + y * DY, DX + 1, DY + 1 ); } class Link { public byte x; public byte y; public Link(int i, int j) { x = (byte)i; y = (byte)j; } }; class Mouse { public byte x; public byte y; public long birth; public long death; public Mouse(int i, int j, long currentTime, long averageLifeSpan) { x = (byte) i; y = (byte) j; birth = currentTime; death = birth + (long) ( Math.random() * 2. * averageLifeSpan ); } }; // Queue of integers (used for keystrokes) class Queue { final int MAX_LENGTH = 16; int maxLength = MAX_LENGTH; int queue[]; int length = 0; int beginning = 0, end = 0; public Queue() { queue = new int[maxLength]; } public Queue(int size) { maxLength = size; queue = new int[maxLength]; } public synchronized void init() { length = 0; beginning = 0; end = 0; } public synchronized int get() { if (length <= 0) return 0; int res = queue[beginning]; beginning++; if (beginning >= maxLength) beginning = 0; length--; return res; } public synchronized void add(int e) { if (length >= maxLength) return; length++; queue[end] = e; end++; if (end >= maxLength) end = 0; } public int length() { return length; } public boolean empty() { return (length <= 0); } }; }