# Expert Java Programming Part IV



## JGuru (Nov 16, 2016)

Expert Java Programming Part 4:

In this tutorial we'll write some cool Java games like BreakOut, PackMan, MineSweeper,  ripple effect, Tetris, Scrabblet, CDShelf.


1) Write a Find MacOS Finder File browser like Stacked view ?


Ans:

You can browse among the albums by clicking on them or by using the arrow keys on your keyboard (left, right, up, down, page up and page down) and also home and end to go directly to the first or last item. The code does support the selection of an item when you press enter or click the one in the center. You can insert your own code into the inner classesKeyAvatarSelector and MouseAvatarSelector to handle the selection. In the case of the login dialog, selecting an item displays the password field:



If I recall correctly, the implemention we used in the chat client allows to register an action listener on the component. But the interesting part here is the 3D effect. You can see it better when browsing the items: as they scroll, their size and opacity changes.

Drawing the layout of the final result helps to understand how to implement the final effect:



As you can see on this poorly drawn schema, the further the items are from the center, the smaller. By doing this we give the impression the items are also further in the distance, on the Z axis. To reinforce this impression, we also want to change the opacity, making the items more and more transparent as they are supposed to be further. To achieve this we need to find a relation between the size/opacity and the location of the item on screen. Unfortunately the answer is mathematics (hate'em #@!).

In this demo, the albums seem to evolve along a bump centered on screen but you can hack the code to make it work with any path. The idea here is to use a simple two-dimensional plot and to project it on the Z axis. Given the position of an item on the X axis, we compute its position on the Y axis of the plot and use the result as the Z position. Consider this picture:



This is the plot of the equation used in the demo. Now imagine the albums move along the curve. The item in the middle will be the highest whereas the items on the edges will lie below. If we put each album on the curve and project the Y axis on the Z axis we know the depth of each album in our virtual world.

Now we can define the position of each album we need to define our world. To make it easier to implement we consider that the center of the screen is the origin of the X axis. Thus the album in the middle is at position x=0. Both left and right egdes of the screen are at positions x=-1.0 andx=1.0. Mapping the positions from the virtual world to the screen space is done during the painting and proves to be really easy with these numbers.

The case of the Z axis (depth) is more interesting. The idea is to make our items disappear in the distance. Since we change both size and opacity, the best thing to do is to get a Z position in the range [0.0, 1.0], 1.0 being meaning full-sized, fully opaque. To sum up, we want a mathematical function complying to these two constraints:
f[-1.0, 1.0] -> [0.0, 1.0]
f(0.0)~=1.0

The function must also give a curve shape you like for the scrolling effect. As I explained during my talk, I remembered one of my teachers showing us how he was using a Gaussian distribution curve to average our grades. And this curve just has the shape I wanted to use for this demo. After playing with it a while I came up with slightly modified version of the equation:



I used this equation to draw the previous plot. You can see two parameters, σ and ρ. The first parameter, σ, controles the shape of the curve. The following two graphs use respectively σ=0.43 and σ=1.0:





This parameter is really important since it can help you define how fast the items disappear in the distance. If you study closely these diagrams, you can see the curves is not at all in the range [0.0, 1.0] on the Y axis. This is why I introduced the second parameter, ρ. Its role is simply to constrain the results in the appropriate range. To compute ρ solve the equation with ρ=1.0. The result of this computation is the ρ you want to use. You can change σ anytime you want from your code by calling cdShelf.setSigma(0.5);. And here is what happens:

```
private double sigma;
private double rho;
    
private double exp_multiplier;
private double exp_member;

public void setSigma(double sigma) {
  this.sigma = sigma;
  this.rho = 1.0;
  computeEquationParts();
  this.rho = computeModifierUnprotected(0.0);
  computeEquationParts();
  this.damaged = true;
  repaint();
}

private void computeEquationParts() {
  exp_multiplier = Math.sqrt(2.0 * Math.PI) / sigma / rho;
  exp_member = 4.0 * sigma * sigma;
}

private double computeModifier(double x) {
  double result = computeModifierUnprotected(x);
  if (result > 1.0) {
      result = 1.0;
  } else if (result < 0.0) {
      result = 0.0;
  }
  return result;
}

private double computeModifierUnprotected(double x) {
  return exp_multiplier * Math.exp((-x * x) / exp_member);
}
```
The methode computeEquationParts() is reponsible for caching the parts of the equation which remains constant with a given σ. Getting the position of an item on the curve is done by calling computeModifier(positionX). This method protects the values to ensure the result is in the expected range. Given the poor precision of our ρ parameter we might get results close to 1.0 and 0.0 but sometimes greater or lower. Note the value 0.0 can be a problem when you compute the size of the album with it, Java2D doesn't like zero-sized pictures  Well, enough mathematics for now. If you take a look at the code, you'll see the component keeps track of the position of each item on the screen and use it to change their size and opacity.

Now let's take a look at how the albums are loaded and drawn on the screen. The component uses two collections, List<Image> avatars for the pictures and List<String> avatarsText for the label of each item. These collections are meant to be filled in by the inner class PictureFinderThread. In this demo, all the pictures and labels are statically loaded but you can easily change this behavior. For each picture to be loaded, we need to create the CD case and the reflection:
avatars.add(fx.createReflectedPicture(fx.createCrystalCase(image)));

We use aninstance of CrystalCaseFactory called fx to perform this rendering operation. I won't explain how it works since I already did it in Swing Glint. Creating the CD case is just a matter of stacking a few pictures together. Finally, when at least avatarAmount items have been loaded, the component selects the one in the middle and starts drawing items:
if (i++ == avatarAmount) {
  setAvatarIndex(avatarAmount / 2);
  startFader();
}

The fader is just a nicety which makes the items appear progressively. CallingsetAvatarIndex() selects the currently selected item, that is the one at the center of the world. This is really important to understand the difference between the selected state and the position. When we scroll through the items, the selected one might be at another position than the center. But let's see how everything is drawn.


```
In paintComponent() you can see the most important lines of code:
if (damaged) {
  drawableAvatars = sortAvatarsByDepth(x, y, width, height);
  damaged = false;
}

drawAvatars(g2, drawableAvatars);

if (drawableAvatars.length > 0) {
  drawAvatarName(g2);
}
```

To increase performances, the component uses to tricks. First, it knows when it has been damaged, that is resized or hidden. In such a case it must rebuild its layout, that is to say it needs to compute the position and state (size/opacity) of each item. But it doesn't need to do this for all the items. We just want to know the state of each drawable item, the ones which are actually on screen. This is the purpose of the array drawableAvatars created bysortAvatarsByDepth():
private DrawableAvatar[] sortAvatarsByDepth(int x, int y,


```
int width, int height) {
  List drawables = new LinkedList();
  for (int i = 0; i < avatars.size(); i++) {
    promoteAvatarToDrawable(drawables,
                            x, y, width, height, i - avatarIndex);
  }

  DrawableAvatar[] drawableAvatars = new DrawableAvatar[drawables.size()];
  drawableAvatars = drawables.toArray(drawableAvatars);
  Arrays.sort(drawableAvatars);
  return drawableAvatars;
}

This method goes through all the available items and attempts to promote them into the drawable list by calling promoteAvatarToDrawable():
private void promoteAvatarToDrawable(List drawables,
                                     int x, int y, int width, int height,
                                     int offset) {

  double spacing = offset * avatarSpacing;
  double avatarPosition = this.avatarPosition + spacing;

  if (avatarIndex + offset < 0 ||
    avatarIndex + offset >= avatars.size()) {
    return;
  }

  Image avatar = avatars.get(avatarIndex + offset);

  int avatarWidth = displayWidth;//avatar.getWidth(null);
  int avatarHeight = displayHeight;//avatar.getHeight(null);

  double result = computeModifier(avatarPosition);
  int newWidth = (int) (avatarWidth * result);
  if (newWidth == 0) {
    return;
  }
  int newHeight = (int) (avatarHeight * result);
  if (newHeight == 0) {
    return;
  }

  double avatar_x = x + (width - newWidth) / 2.0;
  double avatar_y = y + (height - newHeight / 2.0) / 2.0;

  double semiWidth = width / 2.0;
  avatar_x += avatarPosition * semiWidth;

  if (avatar_x >= width || avatar_x < -newWidth) {
    return;
  }
  
  drawables.add(new DrawableAvatar(avatarIndex + offset,
                                   avatar_x, avatar_y,
                                   newWidth, newHeight,
                                   avatarPosition, result));
}

Ouch! Well this code is actually pretty simple. Given an item, it computes its position in the world, taking into account the desired spacing between the items. Once the position in the world is known, the new size of the item is computed using the result of the equation we discovered earlier. Finally, the code translates the world position into pixel position and checks whether it falls between the bounds of the component. Speaking of this, there is small bug in the testavatar_x >= width || avatar_x < -newWidth which doesn't take into account the x and y parameters (defined by the size of the border, if any, set on the component). Nothing really serious though :) Finally, the drawable item is built and added to the list. A drawable item contains the position (on screen and in the world) of the item, its size in pixel and its position on the Z axis, called zOrder in the code. The class DrawableAvatar implements Comparable to sort the items according to their depth (zOrder).

And that's pretty much it! The final step is to draw the promoted items:
private void drawAvatars(Graphics2D g2, DrawableAvatar[] drawableAvatars) {
  for (DrawableAvatar avatar: drawableAvatars) {
    AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 
                                                          (float) avatar.getAlpha());
    g2.setComposite(composite);
    g2.drawImage(avatars.get(avatar.getIndex()),
                 (int) avatar.getX(), (int) avatar.getY(),
                 avatar.getWidth(), avatar.getHeight(), null);
  }
}
```

Since all the information are available from DrawableAvatar, this method doesn't do much. It just honors the opacity before drawing. You can also see we resize the picture at drawing time since it has proven to be the fastest way to resize a picture. If you wondered why the loading of the albums takes so long, it is because of resizing operations happening in memory instead of on screen.

I invite you to take a look at the code of the numerous inner classes to see how to animate the scrolling and how to handle the click on the items. Just know that setPosition() changes the position of the selected item (identified by avatarIndex). The basic idea is to change the position until the newly selected item is in place.

Overall the code is not very complicated but quite long. It is also far from perfect given the time I took to write it and the fact it went through major changes from the first mockup to this demo. I now have 4 versions of this component, each slightly different from the others. This demo is a mix of the first 3 so I know the code is sometimes awkward 

Finally, if you want to do something when enter is pressed or when the selected item is clicked, consider uncommenting removeInputListeners(). For instance, you could display a white veil over the component and show the cover art and some information about the selected album. When you do that, you might want to disable the listeners responsible for scrolling through the albums. Simply remove the listeners and when the user dismisses the information, calladdInputListeners(). We used this technique in the chat client and I hope we'll be able to show you the code very soon.


```
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Sowndar
 */
import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;

public class CDShelf extends JPanel {

    private static final double ANIM_SCROLL_DELAY = 450;
    private static final int CD_SIZE = 148;
    private static final long serialVersionUID = 1L;

    private final int displayWidth = CD_SIZE;
    private final int displayHeight;

    private List<Image> avatars = null;
    private List<String> avatarsText = null;
    private String avatarText = null;

    private boolean loadingDone = false;

    private Thread picturesFinder = null;
    private Timer scrollerTimer = null;
    private Timer faderTimer = null;

    private float veilAlphaLevel = 0.0f;
    private float alphaLevel = 0.0f;
    private float textAlphaLevel = 0.0f;

    private int avatarIndex = -1;
    private double avatarPosition = 0.0;
    private double avatarSpacing = 0.4;
    private int avatarAmount = 5;

    private double sigma;
    private double rho;

    private double exp_multiplier;
    private double exp_member;

    private boolean damaged = true;

    private DrawableAvatar[] drawableAvatars;

    private FocusGrabber focusGrabber;
    private AvatarScroller avatarScroller;
    private MouseAvatarSelector mouseAvatarSelector;
    private CursorChanger cursorChanger;
    private MouseWheelScroller wheelScroller;
    private KeyScroller keyScroller;
    private KeyAvatarSelector keyAvatarSelector;

    private Font avatarFont;
    private CrystalCaseFactory fx;

    public CDShelf() {
        this.displayHeight = (int) (CD_SIZE * 2 / 1.12);
        avatarFont = new Font("Dialog", Font.PLAIN, 24);
        fx = CrystalCaseFactory.getInstance();

        loadAvatars();

        GridBagLayout layout = new GridBagLayout();
        setLayout(layout);

        setSigma(0.5);

        addComponentListener(new DamageManager());

        initInputListeners();
        addInputListeners();
    }

    public void setAmount(int amount) {
        if (amount > avatars.size()) {
            throw new IllegalArgumentException("Too many avatars");
        }
        this.avatarAmount = amount;
        repaint();
    }

    private void setPosition(double position) {
        this.avatarPosition = position;
        this.damaged = true;
        repaint();
    }

    public void setSigma(double sigma) {
        this.sigma = sigma;
        this.rho = 1.0;
        computeEquationParts();
        this.rho = computeModifierUnprotected(0.0);
        computeEquationParts();
        this.damaged = true;
        repaint();
    }

    public void setSpacing(double avatarSpacing) {
        if (avatarSpacing < 0.0 || avatarSpacing > 1.0) {
            throw new IllegalArgumentException("Spacing must be < 1.0 and > 0.0");
        }
        this.avatarSpacing = avatarSpacing;
        this.damaged = true;
        repaint();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(displayWidth * 5, displayHeight * 3);
    }

    @Override
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    @Override
    public boolean isOpaque() {
        return false;
    }

    @Override
    public boolean isFocusable() {
        return true;
    }

    @Override
    protected void paintChildren(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;

        Composite oldComposite = g2.getComposite();
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                veilAlphaLevel));
        super.paintChildren(g);
        g2.setComposite(oldComposite);
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (!isVisible()) {
            return;
        }

        super.paintComponent(g);

        if (!loadingDone && faderTimer == null) {
            return;
        }

        Insets insets = getInsets();

        int x = insets.left;
        int y = insets.top;

        int width = getWidth() - insets.left - insets.right;
        int height = getHeight() - insets.top - insets.bottom;

        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        Composite oldComposite = g2.getComposite();

        if (damaged) {
            drawableAvatars = sortAvatarsByDepth(x, y, width, height);
            damaged = false;
        }

        drawAvatars(g2, drawableAvatars);

        if (drawableAvatars.length > 0) {
            drawAvatarName(g2);
        }

        g2.setComposite(oldComposite);
    }

    private void drawAvatars(Graphics2D g2, DrawableAvatar[] drawableAvatars) {
        for (DrawableAvatar avatar : drawableAvatars) {
            AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                    (float) avatar.getAlpha());
            g2.setComposite(composite);
            g2.drawImage(avatars.get(avatar.getIndex()),
                    (int) avatar.getX(), (int) avatar.getY(),
                    avatar.getWidth(), avatar.getHeight(), null);
        }
    }

    private DrawableAvatar[] sortAvatarsByDepth(int x, int y,
            int width, int height) {
        List<DrawableAvatar> drawables = new LinkedList<>();
        for (int i = 0; i < avatars.size(); i++) {
            promoteAvatarToDrawable(drawables,
                    x, y, width, height, i - avatarIndex);
        }

        DrawableAvatar[] drawableAvatars = new DrawableAvatar[drawables.size()];
        drawableAvatars = drawables.toArray(drawableAvatars);
        Arrays.sort(drawableAvatars);
        return drawableAvatars;
    }

    private void drawAvatarName(Graphics2D g2) {
        Composite composite = g2.getComposite();
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                textAlphaLevel));

        FontRenderContext context = g2.getFontRenderContext();
        TextLayout layout = new TextLayout(avatarText, avatarFont, context);
        Rectangle2D bounds = layout.getBounds();

        double bulletWidth = bounds.getWidth() + 12;
        double bulletHeight = bounds.getHeight() + layout.getDescent() + 4;

        double x = (getWidth() - bulletWidth) / 2.0;
        double y = (getHeight() + CD_SIZE) / 2.0;

        BufferedImage textImage = new BufferedImage((int) bulletWidth,
                (int) bulletHeight,
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2text = textImage.createGraphics();
        g2text.setColor(new Color(0, 0, 0, 170));
        layout.draw(g2text, 6, layout.getAscent() + 1);
        g2text.setColor(Color.WHITE);
        layout.draw(g2text, 6, layout.getAscent());
        g2text.dispose();

        g2.drawImage(fx.createReflectedPicture(textImage,
                fx.createGradientMask((int) bulletWidth,
                (int) bulletHeight)),
                (int) x, (int) y, null);
        g2.setComposite(composite);
    }

    private void promoteAvatarToDrawable(List<DrawableAvatar> drawables,
            int x, int y, int width, int height,
            int offset) {

        double spacing = offset * avatarSpacing;
        double avatarPosition = this.avatarPosition + spacing;

        if (avatarIndex + offset < 0
                || avatarIndex + offset >= avatars.size()) {
            return;
        }

        Image avatar = avatars.get(avatarIndex + offset);

        int avatarWidth = displayWidth;//avatar.getWidth(null);
        int avatarHeight = displayHeight;//avatar.getHeight(null);

        double result = computeModifier(avatarPosition);
        int newWidth = (int) (avatarWidth * result);
        if (newWidth == 0) {
            return;
        }
        int newHeight = (int) (avatarHeight * result);
        if (newHeight == 0) {
            return;
        }

        double avatar_x = x + (width - newWidth) / 2.0;
        double avatar_y = y + (height - newHeight / 2.0) / 2.0;

        double semiWidth = width / 2.0;
        avatar_x += avatarPosition * semiWidth;

        if (avatar_x >= width || avatar_x < -newWidth) {
            return;
        }

        drawables.add(new DrawableAvatar(avatarIndex + offset,
                avatar_x, avatar_y,
                newWidth, newHeight,
                avatarPosition, result));
    }

    private void computeEquationParts() {
        exp_multiplier = Math.sqrt(2.0 * Math.PI) / sigma / rho;
        exp_member = 4.0 * sigma * sigma;
    }

    private double computeModifier(double x) {
        double result = computeModifierUnprotected(x);
        if (result > 1.0) {
            result = 1.0;
        } else if (result < -1.0) {
            result = -1.0;
        }
        return result;
    }

    private double computeModifierUnprotected(double x) {
        return exp_multiplier * Math.exp((-x * x) / exp_member);
    }

    private void addInputListeners() {
        addMouseListener(focusGrabber);
        addMouseListener(avatarScroller);
        addMouseListener(mouseAvatarSelector);
        addMouseMotionListener(cursorChanger);
        addMouseWheelListener(wheelScroller);
        addKeyListener(keyScroller);
        addKeyListener(keyAvatarSelector);
    }

    private void initInputListeners() {
        // input listeners are all stateless
        // hence they can be instantiated once
        focusGrabber = new FocusGrabber();
        avatarScroller = new AvatarScroller();
        mouseAvatarSelector = new MouseAvatarSelector();
        cursorChanger = new CursorChanger();
        wheelScroller = new MouseWheelScroller();
        keyScroller = new KeyScroller();
        keyAvatarSelector = new KeyAvatarSelector();
    }

    private void loadAvatars() {
        avatars = new ArrayList<>();
        avatarsText = new ArrayList<>();

        picturesFinder = new Thread(new PicturesFinderThread());
        picturesFinder.setPriority(Thread.MIN_PRIORITY);
        picturesFinder.start();
    }

    private void setAvatarIndex(int index) {
        avatarIndex = index;
        avatarText = avatarsText.get(index);
    }

    private void scrollBy(int increment) {
        if (loadingDone) {
            setAvatarIndex(avatarIndex + increment);
            if (avatarIndex < 0) {
                setAvatarIndex(0);
            } else if (avatarIndex >= avatars.size()) {
                setAvatarIndex(avatars.size() - 1);
            }
            damaged = true;
            repaint();
        }
    }

    private void scrollAndAnimateBy(int increment) {
        if (loadingDone && (scrollerTimer == null || !scrollerTimer.isRunning())) {
            int index = avatarIndex + increment;
            if (index < 0) {
                index = 0;
            } else if (index >= avatars.size()) {
                index = avatars.size() - 1;
            }

            DrawableAvatar drawable = null;
            if (drawableAvatars != null) {
                for (DrawableAvatar avatar : drawableAvatars) {
                    if (avatar.index == index) {
                        drawable = avatar;
                        break;
                    }
                }
            }

            if (drawable != null) {
                scrollAndAnimate(drawable);
            }
        }
    }

    private void scrollAndAnimate(DrawableAvatar avatar) {
        if (loadingDone) {
            scrollerTimer = new Timer(33, new AutoScroller(avatar));
            scrollerTimer.start();
        }
    }

    private DrawableAvatar getHitAvatar(int x, int y) {
        for (DrawableAvatar avatar : drawableAvatars) {
            Rectangle hit = new Rectangle((int) avatar.getX(), (int) avatar.getY(),
                    avatar.getWidth(), avatar.getHeight() / 2);
            if (hit.contains(x, y)) {
                return avatar;
            }
        }
        return null;
    }

    private void startFader() {
        faderTimer = new Timer(35, new FaderAction());
        faderTimer.start();
    }

    private class PicturesFinderThread implements Runnable {

        private List<URL> artworks;

        public PicturesFinderThread() {
            artworks = new LinkedList<>();
            artworks.add(getClass().getResource("artworks/Black Eyed Peas.jpg"));
            avatarsText.add("Black Eyed Peas");
            artworks.add(getClass().getResource("artworks/Coldplay.jpg"));
            avatarsText.add("Coldplay");
            artworks.add(getClass().getResource("artworks/Foo Fighters.jpg"));
            avatarsText.add("Foo Fighters");
            artworks.add(getClass().getResource("artworks/Gorillaz.jpg"));
            avatarsText.add("Gorillaz");
            artworks.add(getClass().getResource("artworks/Green Day.jpg"));
            avatarsText.add("Green Day");
            artworks.add(getClass().getResource("artworks/Moby.jpg"));
            avatarsText.add("Moby");
            artworks.add(getClass().getResource("artworks/Norah Jones.jpg"));
            avatarsText.add("Norah Jones");
            artworks.add(getClass().getResource("artworks/Shivaree.jpg"));
            avatarsText.add("Shivaree");
            artworks.add(getClass().getResource("artworks/Sin City.jpg"));
            avatarsText.add("Sin City");
        }

        @Override
        public void run() {
            int i = 0;
            for (URL url : artworks) {
                try {
                    BufferedImage image = ImageIO.read(url);
                    avatars.add(fx.createReflectedPicture(fx.createCrystalCase(image)));
                } catch (IOException e) {
                }

                if (i++ == avatarAmount) {
                    setAvatarIndex(avatarAmount / 2);
                    startFader();
                }
            }

            loadingDone = true;
        }
    }

    private class FaderAction implements ActionListener {

        private long start = 0;

        private FaderAction() {
            alphaLevel = 0.0f;
            textAlphaLevel = 0.0f;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (start == 0) {
                start = System.currentTimeMillis();
            }

            alphaLevel = (System.currentTimeMillis() - start) / 500.0f;
            textAlphaLevel = alphaLevel;
            if (alphaLevel > 1.0f) {
                alphaLevel = 1.0f;
                textAlphaLevel = 1.0f;
                faderTimer.stop();
            }

            repaint();
        }
    }

    private class DrawableAvatar implements Comparable {

        private int index;
        private double x;
        private double y;
        private int width;
        private int height;
        private double zOrder;
        private double position;

        private DrawableAvatar(int index,
                double x, double y, int width, int height,
                double position, double zOrder) {
            this.index = index;
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.position = position;
            this.zOrder = zOrder;
        }

        @Override
        public int compareTo(Object o) {
            double zOrder2 = ((DrawableAvatar) o).zOrder;
            if (zOrder < zOrder2) {
                return -1;
            } else if (zOrder > zOrder2) {
                return 1;
            }
            return 0;
        }

        public double getPosition() {
            return position;
        }

        public double getAlpha() {
            return zOrder * alphaLevel;
        }

        public int getHeight() {
            return height;
        }

        public int getIndex() {
            return index;
        }

        public int getWidth() {
            return width;
        }

        public double getX() {
            return x;
        }

        public double getY() {
            return y;
        }
    }

    private class MouseWheelScroller implements MouseWheelListener {

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            int increment = e.getWheelRotation();
            scrollAndAnimateBy(increment);
        }
    }

    private class KeyScroller extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent e) {
            int keyCode = e.getKeyCode();
            switch (keyCode) {
                case KeyEvent.VK_LEFT:
                case KeyEvent.VK_UP:
                    scrollAndAnimateBy(-1);
                    break;
                case KeyEvent.VK_RIGHT:
                case KeyEvent.VK_DOWN:
                    scrollAndAnimateBy(1);
                    break;
                case KeyEvent.VK_END:
                    scrollBy(avatars.size() - avatarIndex - 1);
                    break;
                case KeyEvent.VK_HOME:
                    scrollBy(-avatarIndex - 1);
                    break;
                case KeyEvent.VK_PAGE_UP:
                    scrollAndAnimateBy(-avatarAmount / 2);
                    break;
                case KeyEvent.VK_PAGE_DOWN:
                    scrollAndAnimateBy(avatarAmount / 2);
                    break;
            }
        }
    }

    private class FocusGrabber extends MouseAdapter {

        @Override
        public void mouseClicked(MouseEvent e) {
            requestFocus();
        }
    }

    private class AvatarScroller extends MouseAdapter {

        @Override
        public void mouseClicked(MouseEvent e) {
            if ((scrollerTimer != null && scrollerTimer.isRunning())
                    || drawableAvatars == null) {
                return;
            }

            if (e.getButton() == MouseEvent.BUTTON1) {
                DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY());
                if (avatar != null && avatar.getIndex() != avatarIndex) {
                    scrollAndAnimate(avatar);
                }
            }
        }
    }

    private class DamageManager extends ComponentAdapter {

        @Override
        public void componentResized(ComponentEvent e) {
            damaged = true;
        }
    }

    private class AutoScroller implements ActionListener {

        private double position;
        private int index;
        private long start;

        private AutoScroller(DrawableAvatar avatar) {
            this.index = avatar.getIndex();
            this.position = avatar.getPosition();
            this.start = System.currentTimeMillis();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed < ANIM_SCROLL_DELAY / 2.0) {
                textAlphaLevel = (float) (1.0 - 2.0 * (elapsed / ANIM_SCROLL_DELAY));
            } else {
                avatarText = avatarsText.get(index);
                textAlphaLevel = (float) (((elapsed / ANIM_SCROLL_DELAY) - 0.5) * 2.0);
                if (textAlphaLevel > 1.0f) {
                    textAlphaLevel = 1.0f;
                }
            }
            if (textAlphaLevel < 0.1f) {
                textAlphaLevel = 0.1f;
            }
            double newPosition = (elapsed / ANIM_SCROLL_DELAY) * -position;

            if (elapsed >= ANIM_SCROLL_DELAY) {
                ((Timer) e.getSource()).stop();
                setAvatarIndex(index);
                setPosition(0.0);
                return;
            }

            setPosition(newPosition);
        }
    }

    private class CursorChanger extends MouseMotionAdapter {

        @Override
        public void mouseMoved(MouseEvent e) {
            if ((scrollerTimer != null && scrollerTimer.isRunning())
                    || drawableAvatars == null) {
                return;
            }

            DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY());
            if (avatar != null) {
                getParent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            } else {
                getParent().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        }
    }

    private class KeyAvatarSelector extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent e) {
            if ((scrollerTimer == null || !scrollerTimer.isRunning())
                    && drawableAvatars != null) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                }
            }
        }
    }

    private class MouseAvatarSelector extends MouseAdapter {

        @Override
        public void mouseClicked(MouseEvent e) {
            if ((scrollerTimer == null || !scrollerTimer.isRunning())
                    && drawableAvatars != null) {
                if (e.getButton() == MouseEvent.BUTTON1) {
                    DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY());
                    if (avatar != null && avatar.getIndex() == avatarIndex) {
                    }
                }
            }
        }
    }
}

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Sowndar
 */
import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;

public class GradientPanel extends JPanel {

    protected BufferedImage gradientImage;
    protected Color gradientStart = new Color(110, 110, 110);
    protected Color gradientEnd = new Color(0, 0, 0);

    public GradientPanel() {
        this(new BorderLayout());
    }

    public GradientPanel(LayoutManager layout) {
        super(layout);
        addComponentListener(new GradientCacheManager());
    }

    @Override
    protected void paintComponent(Graphics g) {
        createImageCache();

        if (gradientImage != null) {
            Shape clip = g.getClip();
            Rectangle bounds = clip.getBounds();

            Image backgroundImage = gradientImage.getSubimage(bounds.x,
                    bounds.y,
                    bounds.width,
                    bounds.height);
            g.drawImage(backgroundImage, bounds.x, bounds.y, null);
        }
    }

    protected void createImageCache() {
        int width = getWidth();
        int height = getHeight();

        if (width == 0 || height == 0) {
            return;
        }

        if (gradientImage == null
                || width != gradientImage.getWidth()
                || height != gradientImage.getHeight()) {

            gradientImage = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_RGB);

            Graphics2D g2 = gradientImage.createGraphics();
            GradientPaint painter = new GradientPaint(0, 0, gradientEnd,
                    0, height / 2, gradientStart);
            g2.setPaint(painter);
            Rectangle2D rect = new Rectangle2D.Double(0, 0, width, height / 2.0);
            g2.fill(rect);

            painter = new GradientPaint(0, height / 2, gradientStart,
                    0, height, gradientEnd);
            g2.setPaint(painter);
            rect = new Rectangle2D.Double(0, (height / 2.0) - 1.0, width, height);
            g2.fill(rect);

            g2.dispose();
        }
    }

    private void disposeImageCache() {
        synchronized (gradientImage) {
            gradientImage.flush();
            gradientImage = null;
        }
    }

    private class GradientCacheManager implements ComponentListener {

        @Override
        public void componentResized(ComponentEvent e) {
        }

        @Override
        public void componentMoved(ComponentEvent e) {
        }

        @Override
        public void componentShown(ComponentEvent e) {
        }

        @Override
        public void componentHidden(ComponentEvent e) {
            disposeImageCache();
        }
    }
}

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Sowndar
 */
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;

public class CrystalCaseFactory {

    private static CrystalCaseFactory instance = null;
    private BufferedImage cdCase;
    private BufferedImage stitch;
    private BufferedImage reflections;

    private BufferedImage mask;

    public static CrystalCaseFactory getInstance() {
        if (instance == null) {
            instance = new CrystalCaseFactory();
        }
        return instance;
    }

    private CrystalCaseFactory() {
        loadPictures();
    }

    public BufferedImage createCrystalCase(Image cover) {
        BufferedImage crystal = new BufferedImage(cdCase.getWidth(),
                cdCase.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = crystal.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        g2.drawImage(cover, 19, 3, 240, 227, null);
        g2.drawImage(reflections, 0, 0, null);
        g2.drawImage(stitch, 19, 3, null);
        g2.drawImage(cdCase, 0, 0, null);

        g2.dispose();

        return crystal;
    }

    private void loadPictures() {
        try {
            cdCase = ImageIO.read(getClass().getResource("resources/cd_case.png"));
            stitch = ImageIO.read(getClass().getResource("resources/stitch.png"));
            reflections = ImageIO.read(getClass().getResource("resources/reflections.png"));
            mask = createGradientMask(cdCase.getWidth(), cdCase.getHeight());
        } catch (IOException e) {
        }
    }

    public BufferedImage createReflectedPicture(BufferedImage avatar) {
        return createReflectedPicture(avatar, mask);
    }

    public BufferedImage createReflectedPicture(BufferedImage avatar,
            BufferedImage alphaMask) {
        int avatarWidth = avatar.getWidth();
        int avatarHeight = avatar.getHeight();

        BufferedImage buffer = createReflection(avatar,
                avatarWidth, avatarHeight);

        applyAlphaMask(buffer, alphaMask, avatarWidth, avatarHeight);

        return buffer;
    }

    private void applyAlphaMask(BufferedImage buffer,
            BufferedImage alphaMask,
            int avatarWidth, int avatarHeight) {

        Graphics2D g2 = buffer.createGraphics();
        g2.setComposite(AlphaComposite.DstOut);
        g2.drawImage(alphaMask, null, 0, avatarHeight);
        g2.dispose();
    }

    private BufferedImage createReflection(BufferedImage avatar,
            int avatarWidth,
            int avatarHeight) {

        BufferedImage buffer = new BufferedImage(avatarWidth, avatarHeight << 1,
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = buffer.createGraphics();

        g.drawImage(avatar, null, null);
        g.translate(0, avatarHeight << 1);

        AffineTransform reflectTransform = AffineTransform.getScaleInstance(1.0, -1.0);
        g.drawImage(avatar, reflectTransform, null);
        g.translate(0, -(avatarHeight << 1));

        g.dispose();

        return buffer;
    }

    public BufferedImage createGradientMask(int avatarWidth, int avatarHeight) {
        BufferedImage gradient = new BufferedImage(avatarWidth, avatarHeight,
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = gradient.createGraphics();
        GradientPaint painter = new GradientPaint(0.0f, 0.0f,
                new Color(1.0f, 1.0f, 1.0f, 0.5f),
                0.0f, avatarHeight / 2.0f,
                new Color(1.0f, 1.0f, 1.0f, 1.0f));
        g.setPaint(painter);
        g.fill(new Rectangle2D.Double(0, 0, avatarWidth, avatarHeight));

        g.dispose();

        return gradient;
    }
}

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Sowndar
 */
import java.awt.HeadlessException;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MainTest extends JFrame {

    public MainTest() throws HeadlessException {
        super("Music Shelf");
        buildContentPane();
        setSize(700, 500);
        setLocationRelativeTo(null);
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void buildContentPane() {
        setLayout(new StackLayout());
        add(new GradientPanel(), StackLayout.BOTTOM);
        add(new CDShelf(), StackLayout.TOP);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new MainTest();
            }
        });
    }
}

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Sowndar
 */
import java.awt.*;
import java.util.LinkedList;
import java.util.List;

public class StackLayout implements LayoutManager2 {

    public static final String BOTTOM = "bottom";
    public static final String TOP = "top";

    private List<Component> components = new LinkedList<>();

    @Override
    public void addLayoutComponent(Component comp, Object constraints) {
        synchronized (comp.getTreeLock()) {
            if (BOTTOM.equals(constraints)) {
                components.add(0, comp);
            } else if (TOP.equals(constraints)) {
                components.add(comp);
            } else {
                components.add(comp);
            }
        }
    }

    @Override
    public void addLayoutComponent(String name, Component comp) {
        addLayoutComponent(comp, TOP);
    }

    @Override
    public void removeLayoutComponent(Component comp) {
        synchronized (comp.getTreeLock()) {
            components.remove(comp);
        }
    }

    @Override
    public float getLayoutAlignmentX(Container target) {
        return 0.5f;
    }

    @Override
    public float getLayoutAlignmentY(Container target) {
        return 0.5f;
    }

    @Override
    public void invalidateLayout(Container target) {
    }

    @Override
    public Dimension preferredLayoutSize(Container parent) {
        synchronized (parent.getTreeLock()) {
            int width = 0;
            int height = 0;

            for (Component comp : components) {
                Dimension size = comp.getPreferredSize();
                width = Math.max(size.width, width);
                height = Math.max(size.height, height);
            }

            Insets insets = parent.getInsets();
            width += insets.left + insets.right;
            height += insets.top + insets.bottom;

            return new Dimension(width, height);
        }
    }

    @Override
    public Dimension minimumLayoutSize(Container parent) {
        synchronized (parent.getTreeLock()) {
            int width = 0;
            int height = 0;

            for (Component comp : components) {
                Dimension size = comp.getMinimumSize();
                width = Math.max(size.width, width);
                height = Math.max(size.height, height);
            }

            Insets insets = parent.getInsets();
            width += insets.left + insets.right;
            height += insets.top + insets.bottom;

            return new Dimension(width, height);
        }
    }

    @Override
    public Dimension maximumLayoutSize(Container target) {
        return new Dimension(Integer.MAX_VALUE,
                Integer.MAX_VALUE);
    }

    @Override
    public void layoutContainer(Container parent) {
        synchronized (parent.getTreeLock()) {
            int width = parent.getWidth();
            int height = parent.getHeight();

            Rectangle bounds = new Rectangle(0, 0, width, height);

            int componentsCount = components.size();

            for (int i = 0; i < componentsCount; i++) {
                Component comp = components.get(i);
                comp.setBounds(bounds);
                parent.setComponentZOrder(comp, componentsCount - i - 1);
            }
        }
    }
}
```


*s13.postimg.org/ozs2sbt0j/CDShelf.png

Download the Source Code with the Images here: *www.progx.org/users/Gfx/musicshelfdemo.zip


2) Write a water ripple effect animation in Java?


```
// Ripple.java (c) 2001 by Paul Falstad, www.falstad.com

import java.io.InputStream;
import java.awt.*;
import java.applet.Applet;
import java.applet.AudioClip;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.File;
import java.net.URL;
import java.util.Random;
import java.util.Arrays;
import java.awt.image.*;
import java.lang.Math;
import java.awt.event.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.StringTokenizer;

class RippleCanvas extends Canvas {
    RippleFrame pg;
    RippleCanvas(RippleFrame p) {
	pg = p;
    }
    public Dimension getPreferredSize() {
	return new Dimension(300,400);
    }
    public void update(Graphics g) {
	pg.updateRipple(g);
    }
    public void paint(Graphics g) {
	pg.updateRipple(g);
    }
};

class RippleLayout implements LayoutManager {
    public RippleLayout() {}
    public void addLayoutComponent(String name, Component c) {}
    public void removeLayoutComponent(Component c) {}
    public Dimension preferredLayoutSize(Container target) {
	return new Dimension(500, 500);
    }
    public Dimension minimumLayoutSize(Container target) {
	return new Dimension(100,100);
    }
    public void layoutContainer(Container target) {
	Insets insets = target.insets();
	int targetw = target.size().width - insets.left - insets.right;
	int cw = targetw* 7/10;
	if (target.getComponentCount() == 1)
	    cw = targetw;
	int targeth = target.size().height - (insets.top+insets.bottom);
	target.getComponent(0).move(insets.left, insets.top);
	target.getComponent(0).resize(cw, targeth);
	int barwidth = targetw - cw;
	cw += insets.left;
	int i;
	int h = insets.top;
	for (i = 1; i < target.getComponentCount(); i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
	Dimension d = m.getPreferredSize();
	if (m instanceof Scrollbar)
	    d.width = barwidth;
	if (m instanceof Choice && d.width > barwidth)
	    d.width = barwidth;
	if (m instanceof Label) {
	    h += d.height/5;
	    d.width = barwidth;
	}
	m.move(cw, h);
	m.resize(d.width, d.height);
	h += d.height;
	    }
	}
    }
};

public class Ripple extends Applet implements ComponentListener {
    static RippleFrame ogf;
    void destroyFrame() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
	repaint();
    }
    boolean started = false;
    public void init() {
	addComponentListener(this);
    }

    public static void main(String args[]) {
	ogf = new RippleFrame(null);
	ogf.init();
    }
        
    void showFrame() {
	if (ogf == null) {
	    started = true;
	    ogf = new RippleFrame(this);
	    ogf.init();
	    repaint();
	}
    }
    
    public void paint(Graphics g) {
	String s = "Applet is open in a separate window.";
	if (!started)
	    s = "Applet is starting.";
	else if (ogf == null)
	    s = "Applet is finished.";
	else if (ogf.useFrame)
	    ogf.triggerShow();
	g.drawString(s, 10, 30);
    }
    
    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) { showFrame(); }
    public void componentResized(ComponentEvent e) {}
    
    public void destroy() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
	repaint();
    }
};

class RippleFrame extends Frame
  implements ComponentListener, ActionListener, AdjustmentListener,
             MouseMotionListener, MouseListener, ItemListener {
    
    Thread engine = null;

    Dimension winSize;
    Image dbimage;
    
    Random random;
    int gridSizeX;
    int gridSizeY;
    int gridSizeXY;
    int gw;
    int windowWidth = 50;
    int windowHeight = 50;
    int windowOffsetX = 0;
    int windowOffsetY = 0;
    int windowBottom = 0;
    int windowRight = 0;
    public static final int sourceRadius = 7;
    public static final double freqMult = .0233333;
    
    public String getAppletInfo() {
	return "Ripple by Paul Falstad";
    }

    Container main;
    Button blankButton;
    Button blankWallsButton;
    Button borderButton;
    Button exportButton;
    Checkbox stoppedCheck;
    Checkbox fixedEndsCheck;
    Checkbox view3dCheck;
    Choice modeChooser;
    Choice sourceChooser;
    Choice setupChooser;
    Choice colorChooser;
    Vector setupList;
    Setup setup;
    Scrollbar dampingBar;
    Scrollbar speedBar;
    Scrollbar freqBar;
    Scrollbar resBar;
    Scrollbar brightnessBar;
    Scrollbar auxBar;
    Label auxLabel;
    double dampcoef;
    double freqTimeZero;
    double movingSourcePos = 0;
    double brightMult = 1;
    static final double pi = 3.14159265358979323846;
    float func[];
    float funci[];
    float damp[];
    boolean walls[];
    boolean exceptional[];
    int medium[];
    OscSource sources[];
    static final int MODE_SETFUNC = 0;
    static final int MODE_WALLS = 1;
    static final int MODE_MEDIUM = 2;
    static final int MODE_FUNCHOLD = 3;
    int dragX, dragY, dragStartX = -1, dragStartY;
    int selectedSource = -1;
    int sourceIndex;
    int freqBarValue;
    boolean dragging;
    boolean dragClear;
    boolean dragSet;
    public boolean useFrame;
    boolean showControls;
    double t;
    MemoryImageSource imageSource;
    int pixels[];
    int sourceCount = -1;
    boolean sourcePlane = false;
    boolean sourceMoving = false;
    boolean increaseResolution = false;
    boolean adjustResolution = true;
    int sourceFreqCount = -1;
    int sourceWaveform = SWF_SIN;
    int auxFunction;
    long startTime;
    Color wallColor, posColor, negColor, zeroColor,
	medColor, posMedColor, negMedColor, sourceColor;
    Color schemeColors[][];
    Method timerMethod;
    int timerDiv;
    ImportDialog impDialog;
    static final int mediumMax = 191;
    static final double mediumMaxIndex = .5;
    static final int SWF_SIN = 0;
    static final int SWF_SQUARE = 1;
    static final int SWF_PULSE = 2;
    static final int AUX_NONE = 0;
    static final int AUX_PHASE = 1;
    static final int AUX_FREQ = 2;
    static final int AUX_SPEED = 3;
    static final int SRC_NONE = 0;
    static final int SRC_1S1F = 1;
    static final int SRC_2S1F = 3;
    static final int SRC_2S2F = 4;
    static final int SRC_4S1F = 6;
    static final int SRC_1S1F_PULSE = 8;
    static final int SRC_1S1F_MOVING = 9;
    static final int SRC_1S1F_PLANE = 10;
    static final int SRC_2S1F_PLANE = 12;
    static final int SRC_1S1F_PLANE_PULSE = 14;
    static final int SRC_1S1F_PLANE_PHASE = 15;
    static final int SRC_6S1F = 16;
    static final int SRC_8S1F = 17;
    static final int SRC_10S1F = 18;
    static final int SRC_12S1F = 19;
    static final int SRC_16S1F = 20;
    static final int SRC_20S1F = 21;

    int getrand(int x) {
	int q = random.nextInt();
	if (q < 0) q = -q;
	return q % x;
    }
    RippleCanvas cv;
    Ripple applet;

    RippleFrame(Ripple a) {
	super("Ripple Tank Applet v1.7f");
	applet = a;
	useFrame = true;
	showControls = true;
	adjustResolution = true;
    }
    
    boolean useBufferedImage = false;

    public void init() {
	try {
	    if (applet != null) {
	String param = applet.getParameter("useFrame");
	if (param != null && param.equalsIgnoreCase("false"))
	    useFrame = false;
	param = applet.getParameter("showControls");
	if (param != null && param.equalsIgnoreCase("false"))
	    showControls = false;
	    }
	} catch (Exception e) { e.printStackTrace(); }
	if (useFrame)
	    main = this;
	else
	    main = applet;

	setupList = new Vector();
	Setup s = new SingleSourceSetup();
	while (s != null) {
	    setupList.addElement(s);
	    s = s.createNext();
	}
	String os = System.getProperty("os.name");
	int res = 110;
        String jv = System.getProperty("java.class.version");
        double jvf = new Double(jv).doubleValue();
        if (jvf >= 48)
	    useBufferedImage = true;

	try {
	    Class sysclass = Class.forName("java.lang.System");
	    timerMethod = sysclass.getMethod("nanoTime", null);
	    timerDiv = 1000000;
	    if (timerMethod == null) {
	timerMethod = sysclass.getMethod("currentTimeMillis", null);
	timerDiv = 1;
	    }
	} catch (Exception ee) {
	    ee.printStackTrace();
	}
	
	sources = new OscSource[20];
	main.setLayout(new RippleLayout());
	cv = new RippleCanvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	main.add(cv);

	setupChooser = new Choice();
	int i;
	for (i = 0; i != setupList.size(); i++)
	    setupChooser.add("Setup: " +
	     ((Setup) setupList.elementAt(i)).getName());
	setupChooser.addItemListener(this);
	if (showControls)
	    main.add(setupChooser);

	//add(new Label("Source mode:", Label.CENTER));
	sourceChooser = new Choice();
	sourceChooser.add("No Sources");
	sourceChooser.add("1 Src, 1 Freq");
	sourceChooser.add("1 Src, 2 Freq");
	sourceChooser.add("2 Src, 1 Freq");
	sourceChooser.add("2 Src, 2 Freq");
	sourceChooser.add("3 Src, 1 Freq");
	sourceChooser.add("4 Src, 1 Freq");
	sourceChooser.add("1 Src, 1 Freq (Square)");
	sourceChooser.add("1 Src, 1 Freq (Pulse)");
	sourceChooser.add("1 Moving Src");
	sourceChooser.add("1 Plane Src, 1 Freq");
	sourceChooser.add("1 Plane Src, 2 Freq");
	sourceChooser.add("2 Plane Src, 1 Freq");
	sourceChooser.add("2 Plane Src, 2 Freq");
	sourceChooser.add("1 Plane 1 Freq (Pulse)");
	sourceChooser.add("1 Plane 1 Freq w/Phase");
	sourceChooser.add("6 Src, 1 Freq");
	sourceChooser.add("8 Src, 1 Freq");
	sourceChooser.add("10 Src, 1 Freq");
	sourceChooser.add("12 Src, 1 Freq");
	sourceChooser.add("16 Src, 1 Freq");
	sourceChooser.add("20 Src, 1 Freq");
	sourceChooser.select(SRC_1S1F);
	sourceChooser.addItemListener(this);
	if (showControls)
	    main.add(sourceChooser);

	//add(new Label("Mouse mode:", Label.CENTER));
	modeChooser = new Choice();
	modeChooser.add("Mouse = Edit Wave");
	modeChooser.add("Mouse = Edit Walls");
	modeChooser.add("Mouse = Edit Medium");
	modeChooser.add("Mouse = Hold Wave");
	modeChooser.addItemListener(this);
	if (showControls)
	    main.add(modeChooser);
	else
	    modeChooser.select(1);

	colorChooser = new Choice();
	colorChooser.addItemListener(this);
	if (showControls)
	    main.add(colorChooser);
	
	blankButton = new Button("Clear Waves");
	if (showControls)
	    main.add(blankButton);
	blankButton.addActionListener(this);
	blankWallsButton = new Button("Clear Walls");
	if (showControls)
	    main.add(blankWallsButton);
	blankWallsButton.addActionListener(this);
	borderButton = new Button("Add Border");
	if (showControls)
	    main.add(borderButton);
	borderButton.addActionListener(this);
	exportButton = new Button("Import/Export");
	if (showControls)
	    main.add(exportButton);
	exportButton.addActionListener(this);
	stoppedCheck = new Checkbox("Stopped");
	stoppedCheck.addItemListener(this);
	if (showControls)
	    main.add(stoppedCheck);
	fixedEndsCheck = new Checkbox("Fixed Edges", true);
	fixedEndsCheck.addItemListener(this);
	if (showControls)
	    main.add(fixedEndsCheck);
	
	view3dCheck = new Checkbox("3-D View");
	view3dCheck.addItemListener(this);
	if (showControls)
	    main.add(view3dCheck);

	Label l = new Label("Simulation Speed", Label.CENTER);
	speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 8, 1, 1, 100);
	if (showControls) {
	    main.add(l);
	    main.add(speedBar);
	}
   
	speedBar.addAdjustmentListener(this);

	l = new Label("Resolution", Label.CENTER);
	resBar = new Scrollbar(Scrollbar.HORIZONTAL, res, 5, 5, 400);
	if (showControls) {
	    main.add(l);
	    main.add(resBar);
	}
	resBar.addAdjustmentListener(this);
	setResolution();

	l = new Label("Damping", Label.CENTER);
	dampingBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 2, 100);
	dampingBar.addAdjustmentListener(this);
	if (showControls) {
	    main.add(l);
	    main.add(dampingBar);
	}

	l = new Label("Source Frequency", Label.CENTER);
	freqBar = new Scrollbar(Scrollbar.HORIZONTAL,
	freqBarValue = 15, 1, 1, 30);
	freqBar.addAdjustmentListener(this);
	if (showControls) {
	    main.add(l);
	    main.add(freqBar);
	}

	l = new Label("Brightness", Label.CENTER);
	brightnessBar = new Scrollbar(Scrollbar.HORIZONTAL,
	      27, 1, 1, 1200);
	brightnessBar.addAdjustmentListener(this);
	if (showControls) {
	    main.add(l);
	    main.add(brightnessBar);
	}

	auxLabel = new Label("", Label.CENTER);
	auxBar = new Scrollbar(Scrollbar.HORIZONTAL, 1, 1, 1, 30);
	auxBar.addAdjustmentListener(this);
	if (showControls) {
	    main.add(auxLabel);
	    main.add(auxBar);
	}

	if (showControls)
	    main.add(new Label("*www.falstad.com"));

	schemeColors = new Color[20][8];
	
	try {
	    String param;
	    param = applet.getParameter("setup");
	    if (param != null)
	setupChooser.select(Integer.parseInt(param));
	    param = applet.getParameter("setupClass");
	    if (param != null) {
	for (i = 0; i != setupList.size(); i++) {
	    if (setupList.elementAt(i).getClass().getName().
	equalsIgnoreCase("RippleFrame$" + param))
	break;
	}
	if (i != setupList.size())
	    setupChooser.select(i);
	    }
	    for (i = 0; i != 20; i++) {
	param = applet.getParameter("colorScheme" + (i+1));
	if (param == null)
	    break;
	decodeColorScheme(i, param);
	    }
	} catch (Exception e) { if (applet != null) e.printStackTrace(); }
	if (colorChooser.getItemCount() == 0)
	    addDefaultColorScheme();
	doColor();
	random = new Random();
	setDamping();
	setup = (Setup) setupList.elementAt(setupChooser.getSelectedIndex());
	reinit();
	cv.setBackground(Color.black);
	cv.setForeground(Color.lightGray);
	startTime = getTimeMillis();

	if (useFrame) {
	    resize(800, 640);
	    handleResize();
	    Dimension x = getSize();
	    Dimension screen = getToolkit().getScreenSize();
	    setLocation((screen.width  - x.width)/2,
	(screen.height - x.height)/2);
	    show();
	} else {
	    hide();
	    handleResize();
	    applet.validate();
	}
	main.requestFocus();
    }

    void reinit() { reinit(true); }
    
    void reinit(boolean setup) {
	sourceCount = -1;
	System.out.print("reinit " + gridSizeX + " " + gridSizeY + "\n");
	gridSizeXY = gridSizeX*gridSizeY;
	gw = gridSizeY;
	func  = new float[gridSizeXY];
	funci = new float[gridSizeXY];
	damp = new float[gridSizeXY];
	exceptional = new boolean[gridSizeXY];
	medium = new int[gridSizeXY];
	walls = new boolean[gridSizeXY];
	int i, j;
	for (i = 0; i != gridSizeXY; i++)
	    damp[i] = 1f; // (float) dampcoef;
	for (i = 0; i != windowOffsetX; i++)
	    for (j = 0; j != gridSizeX; j++)
	damp[i+j*gw] = damp[gridSizeX-1-i+gw*j] =
	    damp[j+gw*i] = damp[j+(gridSizeY-1-i)*gw] =
	    (float) (.999-(windowOffsetX-i) * .002);
	if (setup)
	    doSetup();
	
    }

    boolean shown = false;
    
    public void triggerShow() {
	if (!shown)
	    show();
	shown = true;
    }

    void handleResize() {
        Dimension d = winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	pixels = null;
	if (useBufferedImage) {
	    try {
	/* simulate the following code using reflection:
	   dbimage = new BufferedImage(d.width, d.height,
	   BufferedImage.TYPE_INT_RGB);
	   DataBuffer db = (DataBuffer)(((BufferedImage)dbimage).
	   getRaster().getDataBuffer());
	   DataBufferInt dbi = (DataBufferInt) db;
	   pixels = dbi.getData();
	*/
	Class biclass = Class.forName("java.awt.image.BufferedImage");
	Class dbiclass = Class.forName("java.awt.image.DataBufferInt");
	Class rasclass = Class.forName("java.awt.image.Raster");
	Constructor cstr = biclass.getConstructor(
	    new Class[] { int.class, int.class, int.class });
	dbimage = (Image) cstr.newInstance(new Object[] {
	    new Integer(d.width), new Integer(d.height),
	    new Integer(BufferedImage.TYPE_INT_RGB)});
	Method m = biclass.getMethod("getRaster", null);
	Object ras = m.invoke(dbimage, null);
	Object db = rasclass.getMethod("getDataBuffer", null).
	    invoke(ras, null);
	pixels = (int[])
	    dbiclass.getMethod("getData", null).invoke(db, null);
	    } catch (Exception ee) {
	// ee.printStackTrace();
	System.out.println("BufferedImage failed");
	    }
	}
	if (pixels == null) {
	    pixels = new int[d.width*d.height];
	    int i;
	    for (i = 0; i != d.width*d.height; i++)
	pixels[i] = 0xFF000000;
	    imageSource = new MemoryImageSource(d.width, d.height, pixels, 0,
	d.width);
	    imageSource.setAnimated(true);
	    imageSource.setFullBufferUpdates(true);
	    dbimage = cv.createImage(imageSource);
	}
    }

    public boolean handleEvent(Event ev) {
        if (ev.id == Event.WINDOW_DESTROY) {
            destroyFrame();
            return true;
        }
        return super.handleEvent(ev);
    }

    void destroyFrame() {
	if (applet == null)
	    dispose();
	else
	    applet.destroyFrame();
    }
    
    void doBlank() {
	int x, y;
	// I set all the elements in the grid to 1e-10 instead of 0 because
	// if I set them to zero, then the simulation slows down for a
	// short time until the grid fills up again.  Don't ask me why!
	// I don't know.  This showed up when I started using floats
	// instead of doubles.
	for (x = 0; x != gridSizeXY; x++)
	    func[x] = funci[x] = 1e-10f;
    }

    void doBlankWalls() {
	int x, y;
	for (x = 0; x != gridSizeXY; x++) {
	    walls[x] = false;
	    medium[x] = 0;
	}
	calcExceptions();
    }

    void doBorder() {
	int x, y;
	for (x = 0; x < gridSizeX; x++) {
	    setWall(x, windowOffsetY);
	    setWall(x, windowBottom);
	}
	for (y = 0; y < gridSizeY; y++) {
	    setWall(windowOffsetX, y);
	    setWall(windowRight,   y);
	}
	calcExceptions();
    }

    void setWall(int x, int y) { walls[x+gw*y] = true; }
    void setWall(int x, int y, boolean b) { walls[x+gw*y] = b; }
    void setMedium(int x, int y, int q) { medium[x+gw*y] = q; }

    long getTimeMillis() {
	try {
	    Long time = (Long) timerMethod.invoke(null, new Object[] { } );
	    return time.longValue() / timerDiv;
	} catch (Exception ee) { ee.printStackTrace(); return 0; } 
    }
    
    void calcExceptions() {
	int x, y;
	// if walls are in place on border, need to extend that through
	// hidden area to avoid "leaks"
	for (x = 0; x != gridSizeX; x++)
	    for (y = 0; y < windowOffsetY; y++) {
	walls[x+gw*y] = walls[x+gw*windowOffsetY];
	walls[x+gw*(gridSizeY-y-1)] =
	    walls[x+gw*(gridSizeY-windowOffsetY-1)];
	    }
	for (y = 0; y < gridSizeY; y++)
	    for (x = 0; x < windowOffsetX; x++) {
	walls[x+gw*y] = walls[windowOffsetX+gw*y];
	walls[gridSizeX-x-1+gw*y] =
	    walls[gridSizeX-windowOffsetX-1+gw*y];
	    }
	// generate exceptional array, which is useful for doing
	// special handling of elements
	for (x = 1; x < gridSizeX-1; x++)
	    for (y = 1; y < gridSizeY-1; y++) {
	int gi = x+gw*y;
	exceptional[gi] =
	    walls[gi-1] || walls[gi+1] ||
	    walls[gi-gw] || walls[gi+gw] || walls[gi] ||
	    medium[gi] != medium[gi-1] ||
	    medium[gi] != medium[gi+1];
	if ((x == 1 || x == gridSizeX-2) &&
	    medium[gi] != medium[gridSizeX-1-x+gw*(y+1)] ||
	    medium[gi] != medium[gridSizeX-1-x+gw*(y-1)])
	    exceptional[gi] = true;
	    }
	// put some extra exceptions at the corners to ensure tadd2, sinth,
	// etc get calculated
	exceptional[1+gw] = exceptional[gridSizeX-2+gw] =
	    exceptional[1+(gridSizeY-2)*gw] =
	    exceptional[gridSizeX-2+(gridSizeY-2)*gw] = true;
    }

    void centerString(Graphics g, String s, int y) {
	FontMetrics fm = g.getFontMetrics();
        g.drawString(s, (winSize.width-fm.stringWidth(s))/2, y);
    }

    public void paint(Graphics g) {
	cv.repaint();
    }

    long lastTime = 0, lastFrameTime, secTime = 0;
    int frames = 0;
    int steps = 0;
    int framerate = 0, steprate = 0;
    
    boolean moveRight = true;
    boolean moveDown = true;
    public void updateRipple(Graphics realg) {
	if (winSize == null || winSize.width == 0) {
	    // this works around some weird bug in IE which causes the
	    // applet to not show up properly sometimes.
	    handleResize();
	    return;
	}
	if (increaseResolution) {
	    increaseResolution = false;
	    if (resBar.getValue() < 495)
	setResolution(resBar.getValue() + 10);
	}
	long sysTime = getTimeMillis();
	double tadd = 0;
	if (!stoppedCheck.getState()) {
	    int val = 5; //speedBar.getValue();
	    tadd = val*.05;
	}
	int i, j;

	boolean stopFunc =
	    dragging && selectedSource == -1 &&
	    view3dCheck.getState() == false &&
	    modeChooser.getSelectedIndex() == MODE_SETFUNC;
	if (stoppedCheck.getState())
	    stopFunc = true;
	int iterCount = speedBar.getValue();
	if (!stopFunc) {
	    /*
	    long sysTime = System.currentTimeMillis();
	    if (sysTime-secTime >= 1000) {
	framerate = frames; steprate = steps;
	frames = 0; steps = 0;
	secTime = sysTime;
	    }
	    lastTime = sysTime;
	    */
	    int iter;
	    int mxx = gridSizeX-1;
	    int mxy = gridSizeY-1;
	    for (iter = 0; iter != iterCount; iter++) {
	int jstart, jend, jinc;
	if (moveDown) {
	    // we process the rows in alternate directions
	    // each time to avoid any directional bias.
	    jstart = 1; jend = mxy; jinc = 1; moveDown = false;
	} else {
	    jstart = mxy-1; jend = 0; jinc = -1; moveDown = true;
	}
	moveRight = moveDown;
	float sinhalfth = 0;
	float sinth = 0;
	float scaleo = 0;
	int curMedium = -1;
	for (j = jstart; j != jend; j += jinc) {
	    int istart, iend, iinc;
	    if (moveRight) {
	iinc = 1; istart = 1; iend = mxx; moveRight = false;
	    } else {
	iinc = -1; istart = mxx-1; iend = 0; moveRight = true;
	    }
	    int gi = j*gw+istart;
	    int giEnd = j*gw+iend;
	    for (; gi != giEnd; gi += iinc) {
	// calculate equilibrum point of this
	// element's oscillation
	float previ = func[gi-1];
	float nexti = func[gi+1];
	float prevj = func[gi-gw];
	float nextj = func[gi+gw];
	float basis = (nexti+previ+nextj+prevj)*.25f;
	if (exceptional[gi]) {
	    if (curMedium != medium[gi]) {
	curMedium = medium[gi];
	double tadd2 = tadd*(1-
	   (mediumMaxIndex/mediumMax)*curMedium);
	sinhalfth = (float)
	    Math.sin(tadd2/2);
	sinth = (float)
	    (Math.sin(tadd2)*dampcoef);
	scaleo = (float) (1-Math.sqrt(
	    4*sinhalfth*sinhalfth-sinth*sinth));
	    }
	    if (walls[gi])
	continue;
	    int count = 4;
	    if (fixedEndsCheck.getState()) {
	if (walls[gi-1]) previ = 0;
	if (walls[gi+1]) nexti = 0;
	if (walls[gi-gw]) prevj = 0;
	if (walls[gi+gw]) nextj = 0;
	    } else {
	if (walls[gi-1])
	    previ = walls[gi+1] ? func[gi] : func[gi+1];
	if (walls[gi+1])
	    nexti = walls[gi-1] ? func[gi] : func[gi-1];
	if (walls[gi-gw])
	    prevj = walls[gi+gw] ? func[gi] : func[gi+gw];
	if (walls[gi+gw])
	    nextj = walls[gi-gw] ? func[gi] : func[gi-gw];
	    }
	    basis = (nexti+previ+nextj+prevj)*.25f;
	}
	// what we are doing here (aside from damping)
	// is rotating the point (func[gi], funci[gi])
	// an angle tadd about the point (basis, 0).
	// Rather than call atan2/sin/cos, we use this
	// faster method using some precomputed info.
	float a = 0;
	float b = 0;
	if (damp[gi] == 1f) {
	    a = func[gi] - basis;
	    b = funci[gi];
	} else {
	    a = (func[gi] - basis) * damp[gi];
	    b = funci[gi] * damp[gi];
	}
	func [gi] = basis + a*scaleo - b*sinth;
	funci[gi] = b*scaleo + a*sinth;
	    }
	}
	t += tadd;
	if (sourceCount > 0) {
	    double w = freqBar.getValue()*(t-freqTimeZero)*freqMult;
	    double w2 = w;
	    boolean skip = false;
	    switch (auxFunction) {
	    case AUX_FREQ:
	w2 = auxBar.getValue()*t*freqMult;
	break;
	    case AUX_PHASE:
	w2 = w+(auxBar.getValue()-1) * (pi/29);
	break;
	    }
	    double v = 0;
	    double v2 = 0;
	    switch (sourceWaveform) {
	    case SWF_SIN:
	v = Math.cos(w);
	if (sourceCount >= (sourcePlane ? 4 : 2))
	    v2 = Math.cos(w2);
	else if (sourceFreqCount == 2)
	    v = (v+Math.cos(w2))*.5;
	break;
	    case SWF_SQUARE:
	w %= pi*2;
	v = (w < pi) ? 1 : -1;
	break;
	    case SWF_PULSE:
	{
	    w %= pi*2;
	    double pulselen = pi/4;
	    double pulselen2 = freqBar.getValue() * .2;
	    if (pulselen2 < pulselen)
	pulselen = pulselen2;
	    v = (w > pulselen) ? 0 :
	Math.sin(w*pi/pulselen);
	    if (w > pulselen*2)
	skip = true;
	}
	break;
	    }
	    for (j = 0; j != sourceCount; j++) {
	if ((j % 2) == 0)
	    sources[j].v = (float) (v*setup.sourceStrength());
	else
	    sources[j].v = (float) (v2*setup.sourceStrength());
	    }
	    if (sourcePlane) {
	if (!skip) {
	    for (j = 0; j != sourceCount/2; j++) {
	OscSource src1 = sources[j*2];
	OscSource src2 = sources[j*2+1];
	OscSource src3 = sources[j];
	drawPlaneSource(src1.x, src1.y,
	src2.x, src2.y, src3.v, w);
	    }
	}
	    } else {
	if (sourceMoving) {
	    int sy;
	    movingSourcePos += tadd*.02*auxBar.getValue();
	    double wm = movingSourcePos;
	    int h = windowHeight-3;
	    wm %= h*2;
	    sy = (int) wm;
	    if (sy > h)
	sy = 2*h-sy;
	    sy += windowOffsetY+1;
	    sources[0].y = sy;
	}
	for (i = 0; i != sourceCount; i++) {
	    OscSource src = sources[i];
	    func[src.x+gw*src.y] = src.v;
	    funci[src.x+gw*src.y] = 0;
	}
	    }
	}
	setup.eachFrame();
	steps++;
	filterGrid();
	    }
	}

	brightMult = Math.exp(brightnessBar.getValue()/100. - 5.);
	if (view3dCheck.getState())
	    draw3dView();
	else
	    draw2dView();

	if (imageSource != null)
	    imageSource.newPixels();

	realg.drawImage(dbimage, 0, 0, this);
	if (dragStartX >= 0 && !view3dCheck.getState()) {
	    int x = dragStartX*windowWidth/winSize.width;
	    int y = windowHeight-1-(dragStartY*windowHeight/winSize.height);
	    String s = "(" + x + "," + y +")";
	    realg.setColor(Color.white);
	    FontMetrics fm = realg.getFontMetrics();
	    int h = 5+fm.getAscent();
	    realg.fillRect(0, winSize.height-h, fm.stringWidth(s)+10, h);
	    realg.setColor(Color.black);
	    realg.drawString(s, 5, winSize.height-5);
	}

	/*frames++;
	realg.setColor(Color.white);
	realg.drawString("Framerate: " + framerate, 10, 10);
	realg.drawString("Steprate: " + steprate,  10, 30);
	lastFrameTime = lastTime;
	*/
	
	if (!stoppedCheck.getState()) {
	    long diff = getTimeMillis()-sysTime;
	    // we want the time it takes for a wave to travel across the screen
	    // to be more-or-less constant, but don't do anything after 5 seconds
	    if (adjustResolution && diff > 0 && sysTime < startTime+1000 &&
	windowOffsetX*diff/iterCount < 55) {
	increaseResolution = true;
	startTime = sysTime;
	    }
	    if (dragging && selectedSource == -1 &&
	modeChooser.getSelectedIndex() == MODE_FUNCHOLD)
	editFuncPoint(dragX, dragY);
	    cv.repaint(0);
	}
    }

    // filter out high-frequency noise
    int filterCount;
    void filterGrid() {
	int x, y;
	if (fixedEndsCheck.getState())
	    return;
	if (sourceCount > 0 && freqBarValue > 23)
	    return;
	if (sourceFreqCount >= 2 && auxBar.getValue() > 23)
	    return;
	if (++filterCount < 10)
	    return;
	filterCount = 0;
	for (y = windowOffsetY; y < windowBottom; y++)
	    for (x = windowOffsetX; x < windowRight; x++) {
	int gi = x+y*gw;
	if (walls[gi])
	    continue;
	if (func[gi-1] < 0 && func[gi] > 0 && func[gi+1] < 0 &&
	    !walls[gi+1] && !walls[gi-1])
	    func[gi] = (func[gi-1]+func[gi+1])/2;
	if (func[gi-gw] < 0 && func[gi] > 0 && func[gi+gw] < 0 &&
	    !walls[gi-gw] && !walls[gi+gw])
	    func[gi] = (func[gi-gw]+func[gi+gw])/2;
	if (func[gi-1] > 0 && func[gi] < 0 && func[gi+1] > 0 &&
	    !walls[gi+1] && !walls[gi-1])
	    func[gi] = (func[gi-1]+func[gi+1])/2;
	if (func[gi-gw] > 0 && func[gi] < 0 && func[gi+gw] > 0 &&
	    !walls[gi-gw] && !walls[gi+gw])
	    func[gi] = (func[gi-gw]+func[gi+gw])/2;
	    }
    }
    
    void plotPixel(int x, int y, int pix) {
	if (x < 0 || x >= winSize.width)
	    return;
	try { pixels[x+y*winSize.width] = pix; } catch (Exception e) {}
    }

    // draw a circle the slow and dirty way
    void plotSource(int n, int xx, int yy) {
	int rad = sourceRadius;
	int j;
	int col = (sourceColor.getRed()<<16)|
	    (sourceColor.getGreen()<<8)|
	    (sourceColor.getBlue())| 0xFF000000;
	if (n == selectedSource)
	    col ^= 0xFFFFFF;
	for (j = 0; j <= rad; j++) {
	    int k = (int) (Math.sqrt(rad*rad-j*j)+.5);
	    plotPixel(xx+j, yy+k, col);
	    plotPixel(xx+k, yy+j, col);
	    plotPixel(xx+j, yy-k, col);
	    plotPixel(xx-k, yy+j, col);
	    plotPixel(xx-j, yy+k, col);
	    plotPixel(xx+k, yy-j, col);
	    plotPixel(xx-j, yy-k, col);
	    plotPixel(xx-k, yy-j, col);
	    plotPixel(xx, yy+j, col);
	    plotPixel(xx, yy-j, col);
	    plotPixel(xx+j, yy, col);
	    plotPixel(xx-j, yy, col);
	}
    }

    void draw2dView() {
	int ix = 0;
	int i, j, k, l;
	for (j = 0; j != windowHeight; j++) {
	    ix = winSize.width*(j*winSize.height/windowHeight);
	    int j2 = j+windowOffsetY;
	    int gi = j2*gw+windowOffsetX;
	    int y = j*winSize.height/windowHeight;
	    int y2 = (j+1)*winSize.height/windowHeight;
	    for (i = 0; i != windowWidth; i++, gi++) {
	int x = i*winSize.width/windowWidth;
	int x2 = (i+1)*winSize.width/windowWidth;
	int i2 = i+windowOffsetX;
	double dy = func[gi] * brightMult;
	if (dy < -1)
	    dy = -1;
	if (dy > 1)
	    dy = 1;
	int col = 0;
	int colR = 0, colG = 0, colB = 0;
	if (walls[gi]) {
	    colR = wallColor.getRed();
	    colG = wallColor.getGreen();
	    colB = wallColor.getBlue();
	} else if (dy < 0) {
	    double d1 = -dy;
	    double d2 = 1-d1;
	    double d3 = medium[gi]*(1/255.01);
	    double d4 = 1-d3;
	    double a1 = d1*d4; double a2 = d2*d4;
	    double a3 = d1*d3; double a4 = d2*d3;
	    colR = (int) (negColor. getRed()    *a1 +
	  zeroColor.getRed()    *a2 +
	  negMedColor.getRed()  *a3 +
	  medColor.getRed()     *a4);
	    colG = (int) (negColor. getGreen()  *a1 +
	  zeroColor.getGreen()  *a2 +
	  negMedColor.getGreen()*a3 +
	  medColor.getGreen()   *a4);
	    colB = (int) (negColor. getBlue()   *a1 +
	  zeroColor.getBlue()   *a2 +
	  negMedColor.getBlue() *a3 +
	  medColor.getBlue()    *a4);
	} else {
	    double d1 = dy;
	    double d2 = 1-dy;
	    double d3 = medium[gi]*(1/255.01);
	    double d4 = 1-d3;
	    double a1 = d1*d4; double a2 = d2*d4;
	    double a3 = d1*d3; double a4 = d2*d3;
	    colR = (int) (posColor. getRed()    *a1 +
	  zeroColor.getRed()    *a2 +
	  posMedColor.getRed()  *a3 +
	  medColor.getRed()     *a4);
	    colG = (int) (posColor. getGreen()  *a1 +
	  zeroColor.getGreen()  *a2 +
	  posMedColor.getGreen()*a3 +
	  medColor.getGreen()   *a4);
	    colB = (int) (posColor. getBlue()   *a1 +
	  zeroColor.getBlue()   *a2 +
	  posMedColor.getBlue() *a3 +
	  medColor.getBlue()    *a4);
	}
	col = (255<<24) | (colR<<16) | (colG<<8) | (colB);
	for (k = 0; k != x2-x; k++, ix++)
	    for (l = 0; l != y2-y; l++)
	pixels[ix+l*winSize.width] = col;
	    }
	}
	int intf = (gridSizeY/2-windowOffsetY)*winSize.height/windowHeight;
	for (i = 0; i != sourceCount; i++) {
	    OscSource src = sources[i];
	    int xx = src.getScreenX();
	    int yy = src.getScreenY();
	    plotSource(i, xx, yy);
	}
    }
	    
    double realxmx, realxmy, realymz, realzmy, realzmx, realymadd, realzmadd;
    double viewAngle = pi, viewAngleDragStart;
    double viewZoom = .775, viewZoomDragStart;
    double viewAngleCos = -1, viewAngleSin = 0;
    double viewHeight = -38, viewHeightDragStart;
    double scalex, scaley;
    int centerX3d, centerY3d;
    int xpoints[] = new int[4], ypoints[] = new int[4];
    final double viewDistance = 66;
    
    void map3d(double x, double y, double z, int xpoints[], int ypoints[], int pt) {
	/*x *= aspectRatio;
	z *= -4;
	x *= 16./sampleCount; y *= 16./sampleCount;
	double realx = x*viewAngleCos + y*viewAngleSin; // range: [-10,10]
	double realy = z-viewHeight;
	double realz = y*viewAngleCos - x*viewAngleSin + viewDistance;*/
	double realx = realxmx*x + realxmy*y;
	double realy = realymz*z + realymadd;
	double realz = realzmx*x + realzmy*y + realzmadd;
	xpoints[pt] = centerX3d + (int) (realx/realz);
	ypoints[pt] = centerY3d - (int) (realy/realz);
    }

    double scaleMult;
    void scaleworld() {
	scalex = viewZoom * (winSize.width/4) * viewDistance / 8;
	scaley = -scalex;
	int y = (int) (scaley*viewHeight/viewDistance);
	/*centerX3d = winSize.x + winSize.width/2;
	  centerY3d = winSize.y + winSize.height/2 - y;*/
	centerX3d = winSize.width/2;
	centerY3d = winSize.height/2 - y;
	scaleMult = 16./(windowWidth/2);
	realxmx = -viewAngleCos*scaleMult * scalex;
	realxmy = viewAngleSin*scaleMult * scalex;
	realymz = -brightMult*scaley;
	realzmy = viewAngleCos*scaleMult;
	realzmx = viewAngleSin*scaleMult;
	realymadd = -viewHeight*scaley;
	realzmadd = viewDistance;
    }

    void draw3dView() {
	int half = gridSizeX/2;
	scaleworld();
	int x, y;
	int xdir, xstart, xend;
	int ydir, ystart, yend;
	int sc = windowRight-1;
	
	// figure out what order to render the grid elements so that
	// the ones in front are rendered first.
	if (viewAngleCos > 0) {
	    ystart = sc;
	    yend = windowOffsetY-1;
	    ydir = -1;
	} else {
	    ystart = windowOffsetY;
	    yend = sc+1;
	    ydir = 1;
	}
	if (viewAngleSin < 0) {
	    xstart = windowOffsetX;
	    xend = sc+1;
	    xdir = 1;
	} else {
	    xstart = sc;
	    xend = windowOffsetX-1;
	    xdir = -1;
	}
	boolean xFirst = (viewAngleSin * xdir < viewAngleCos * ydir);
	
	for (x = 0; x != winSize.width*winSize.height; x++)
	    pixels[x] = 0xFF000000;
	/*double zval = 2.0/sampleCount;
	  System.out.println(zval);
	  if (sampleCount == 128)
	  zval = .1;*/
	double zval = .1;
	double zval2 = zval*zval;
	
	for (x = xstart; x != xend; x += xdir) {
	    for (y = ystart; y != yend; y += ydir) {
	if (!xFirst)
	    x = xstart;
	for (; x != xend; x += xdir) {
	    int gi = x+gw*y;
	    map3d(x-half, y-half, func[gi], xpoints, ypoints, 0);
	    map3d(x+1-half, y-half, func[gi+1],
	  xpoints, ypoints, 1);
	    map3d(x-half, y+1-half, func[gi+gw],
	  xpoints, ypoints, 2);
	    map3d(x+1-half, y+1-half, func[gi+gw+1],
	  xpoints, ypoints, 3);
	    double qx = func[gi+1] -func[gi];
	    double qy = func[gi+gw]-func[gi];
	    // calculate lighting
	    double normdot = (qx+qy+zval)*(1/1.73)/
	Math.sqrt(qx*qx+qy*qy+zval2);
	    int col = computeColor(gi, normdot);
	    fillTriangle(xpoints[0], ypoints[0],
	 xpoints[1], ypoints[1],
	 xpoints[3], ypoints[3], col);
	    fillTriangle(xpoints[0], ypoints[0],
	 xpoints[2], ypoints[2],
	 xpoints[3], ypoints[3], col);
	    if (xFirst)
	break;
	}
	    }
	    if (!xFirst)
	break;
	}
    }
	
    int computeColor(int gix, double c) {
	double h = func[gix]*brightMult;
	if (c < 0)
	    c = 0;
	if (c > 1)
	    c = 1;
	c = .5 + c * .5;
	double redness = (h < 0) ? -h : 0;
	double grnness = (h > 0) ? h : 0;
	if (redness > 1)
	    redness = 1;
	if (grnness > 1)
	    grnness = 1;
	if (grnness < 0)
	    grnness = 0;
	if (redness < 0)
	    redness = 0;
	double grayness = (1-(redness+grnness))*c;
	double grayness2 = grayness;
	if (medium[gix] > 0) {
	    double mm = 1-(medium[gix] * (1/255.01));
	    grayness2 *= mm;
	}
	double gray = .6;
	int ri = (int) ((c*redness+gray*grayness2)*255);
	int gi = (int) ((c*grnness+gray*grayness2)*255);
	int bi = (int) ((gray*grayness)*255);
	return 0xFF000000 | (ri<<16) | (gi<<8) | bi;
    }

    void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3,
	      int col) {
	if (x1 > x2) {
	    if (x2 > x3) {
	// x1 > x2 > x3
	int ay = interp(x1, y1, x3, y3, x2);
	fillTriangle1(x3, y3, x2, y2, ay, col);
	fillTriangle1(x1, y1, x2, y2, ay, col);
	    } else if (x1 > x3) {
	// x1 > x3 > x2
	int ay = interp(x1, y1, x2, y2, x3);
	fillTriangle1(x2, y2, x3, y3, ay, col);
	fillTriangle1(x1, y1, x3, y3, ay, col);
	    } else {
	// x3 > x1 > x2
	int ay = interp(x3, y3, x2, y2, x1);
	fillTriangle1(x2, y2, x1, y1, ay, col);
	fillTriangle1(x3, y3, x1, y1, ay, col);
	    }
	} else {
	    if (x1 > x3) {
	// x2 > x1 > x3
	int ay = interp(x2, y2, x3, y3, x1);
	fillTriangle1(x3, y3, x1, y1, ay, col);
	fillTriangle1(x2, y2, x1, y1, ay, col);
	    } else if (x2 > x3) {
	// x2 > x3 > x1
	int ay = interp(x2, y2, x1, y1, x3);
	fillTriangle1(x1, y1, x3, y3, ay, col);
	fillTriangle1(x2, y2, x3, y3, ay, col);
	    } else {
	// x3 > x2 > x1
	int ay = interp(x3, y3, x1, y1, x2);
	fillTriangle1(x1, y1, x2, y2, ay, col);
	fillTriangle1(x3, y3, x2, y2, ay, col);
	    }
	}
    }

    int interp(int x1, int y1, int x2, int y2, int x) {
	if (x1 == x2)
	    return y1;
	if (x < x1 && x < x2 || x > x1 && x > x2)
	    System.out.print("interp out of bounds\n");
	return (int) (y1+((double) x-x1)*(y2-y1)/(x2-x1));
    }

    void fillTriangle1(int x1, int y1, int x2, int y2, int y3, int col) {
	// x2 == x3
	int dir = (x1 > x2) ? -1 : 1;
	int x = x1;
	if (x < 0) {
	    x = 0;
	    if (x2 < 0)
	return;
	}
	if (x >= winSize.width) {
	    x = winSize.width-1;
	    if (x2 >= winSize.width)
	return;
	}
	if (y2 > y3) {
	    int q = y2;
	    y2 = y3; y3 = q;
	}
	// y2 < y3
	while (x != x2+dir) {
	    // XXX this could be speeded up
	    int ya = interp(x1, y1, x2, y2, x);
	    int yb = interp(x1, y1, x2, y3, x);
	    if (ya < 0)
	ya = 0;
	    if (yb >= winSize.height)
	yb = winSize.height-1;

	    for (; ya <= yb; ya++)
	pixels[x+ya*winSize.width] = col;
	    x += dir;
	    if (x < 0 || x >= winSize.width)
	return;
	}
    }
    
    int abs(int x) {
	return x < 0 ? -x : x;
    }

    void drawPlaneSource(int x1, int y1, int x2, int y2, float v, double w) {
	if (y1 == y2) {
	    if (x1 == windowOffsetX)
	x1 = 0;
	    if (x2 == windowOffsetX)
	x2 = 0;
	    if (x1 == windowOffsetX+windowWidth-1)
	x1 = gridSizeX-1;
	    if (x2 == windowOffsetX+windowWidth-1)
	x2 = gridSizeX-1;
	}
	if (x1 == x2) {
	    if (y1 == windowOffsetY)
	y1 = 0;
	    if (y2 == windowOffsetY)
	y2 = 0;
	    if (y1 == windowOffsetY+windowHeight-1)
	y1 = gridSizeY-1;
	    if (y2 == windowOffsetY+windowHeight-1)
	y2 = gridSizeY-1;
	}

	/*double phase = 0;
	if (sourceChooser.getSelectedIndex() == SRC_1S1F_PLANE_PHASE)
	phase = (auxBar.getValue()-15)*3.8*freqBar.getValue()*freqMult;*/

	// need to draw a line from x1,y1 to x2,y2
	if (x1 == x2 && y1 == y2) {
	    func [x1+gw*y1] = v;
	    funci[x1+gw*y1] = 0;
	} else if (abs(y2-y1) > abs(x2-x1)) {
	    // y difference is greater, so we step along y's
	    // from min to max y and calculate x for each step
	    double sgn = sign(y2-y1);
	    int x, y;
	    for (y = y1; y != y2+sgn; y += sgn) {
	x = x1+(x2-x1)*(y-y1)/(y2-y1);
	double ph = sgn*(y-y1)/(y2-y1);
	int gi = x+gw*y;
	func [gi] = setup.calcSourcePhase(ph, v, w);
	//(phase == 0) ? v :
	//  (float) (Math.sin(w+ph));
	funci[gi] = 0;
	    }
	} else {
	    // x difference is greater, so we step along x's
	    // from min to max x and calculate y for each step
	    double sgn = sign(x2-x1);
	    int x, y;
	    for (x = x1; x != x2+sgn; x += sgn) {
	y = y1+(y2-y1)*(x-x1)/(x2-x1);
	double ph = sgn*(x-x1)/(x2-x1);
	int gi = x+gw*y;
	func [gi] = setup.calcSourcePhase(ph, v, w);
	// (phase == 0) ? v :
	// (float) (Math.sin(w+ph));
	funci[gi] = 0;
	    }
	}
    }

    int sign(int x) {
	return (x < 0) ? -1 : (x == 0) ? 0 : 1;
    }

    void edit(MouseEvent e) {
	if (view3dCheck.getState())
	    return;
	int x = e.getX();
	int y = e.getY();
	if (selectedSource != -1) {
	    x = x*windowWidth/winSize.width;
	    y = y*windowHeight/winSize.height;
	    if (x >= 0 && y >= 0 && x < windowWidth && y < windowHeight) {
	sources[selectedSource].x = x+windowOffsetX;
	sources[selectedSource].y = y+windowOffsetY;
	    }
	    return;
	}
	if (dragX == x && dragY == y)
	    editFuncPoint(x, y);
	else {
	    // need to draw a line from old x,y to new x,y and
	    // call editFuncPoint for each point on that line.  yuck.
	    if (abs(y-dragY) > abs(x-dragX)) {
	// y difference is greater, so we step along y's
	// from min to max y and calculate x for each step
	int x1 = (y < dragY) ? x : dragX;
	int y1 = (y < dragY) ? y : dragY;
	int x2 = (y > dragY) ? x : dragX;
	int y2 = (y > dragY) ? y : dragY;
	dragX = x;
	dragY = y;
	for (y = y1; y <= y2; y++) {
	    x = x1+(x2-x1)*(y-y1)/(y2-y1);
	    editFuncPoint(x, y);
	}
	    } else {
	// x difference is greater, so we step along x's
	// from min to max x and calculate y for each step
	int x1 = (x < dragX) ? x : dragX;
	int y1 = (x < dragX) ? y : dragY;
	int x2 = (x > dragX) ? x : dragX;
	int y2 = (x > dragX) ? y : dragY;
	dragX = x;
	dragY = y;
	for (x = x1; x <= x2; x++) {
	    y = y1+(y2-y1)*(x-x1)/(x2-x1);
	    editFuncPoint(x, y);
	}
	    }
	}
    }

    void editFuncPoint(int x, int y) {
	int xp = x*windowWidth/winSize.width+windowOffsetX;
	int yp = y*windowHeight/winSize.height+windowOffsetY;
	int gi = xp+yp*gw;
	if (modeChooser.getSelectedIndex() == MODE_WALLS) {
	    if (!dragSet && !dragClear) {
	dragClear = walls[gi];
	dragSet = !dragClear;
	    }
	    walls[gi] = dragSet;
	    calcExceptions();
	    func[gi] = funci[gi] = 0;
	} else if (modeChooser.getSelectedIndex() == MODE_MEDIUM) {
	    if (!dragSet && !dragClear) {
	dragClear = medium[gi] > 0;
	dragSet = !dragClear;
	    }
	    medium[gi] = (dragSet) ? mediumMax : 0;
	    calcExceptions();
	} else {
	    if (!dragSet && !dragClear) {
	dragClear = func[gi] > .1;
	dragSet = !dragClear;
	    }
	    func[gi] = (dragSet) ? 1 : -1;
	    funci[gi] = 0;
	}
	cv.repaint(0);
    }

    void selectSource(MouseEvent me) {
	int x = me.getX();
	int y = me.getY();
	int i;
	for (i = 0; i != sourceCount; i++) {
	    OscSource src = sources[i];
	    int sx = src.getScreenX();
	    int sy = src.getScreenY();
	    int r2 = (sx-x)*(sx-x)+(sy-y)*(sy-y);
	    if (sourceRadius*sourceRadius > r2) {
	selectedSource = i;
	return;
	    }
	}
	selectedSource = -1;
    }

    void setDamping() {
	/*int i;
	double damper = dampingBar.getValue() * .00002;// was 5
	dampcoef = Math.exp(-damper);*/
	dampcoef = 1;
    }

    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) {
	cv.repaint();
    }

    public void componentResized(ComponentEvent e) {
	handleResize();
	cv.repaint(100);
    }
    public void actionPerformed(ActionEvent e) {
	if (e.getSource() == blankButton) {
	    doBlank();
	    cv.repaint();
	}
	if (e.getSource() == blankWallsButton) {
	    doBlankWalls();
	    cv.repaint();
	}
	if (e.getSource() == borderButton) {
	    doBorder();
	    cv.repaint();
	}
	if (e.getSource() == exportButton)
	    doImport();
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
	System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
	if (e.getSource() == resBar) {
	    setResolution();
	    reinit();
	}
	if (e.getSource() == dampingBar)
	    setDamping();
	if (e.getSource() == brightnessBar)
	    cv.repaint(0);
	if (e.getSource() == freqBar)
	    setFreq();
    }

    void setFreqBar(int x) {
	freqBar.setValue(x);
	freqBarValue = x;
	freqTimeZero = 0;
    }

    void setFreq() {
	// adjust time zero to maintain continuity in the freq func
	// even though the frequency has changed.
	double oldfreq = freqBarValue * freqMult;
	freqBarValue = freqBar.getValue();
	double newfreq = freqBarValue * freqMult;
	double adj = newfreq-oldfreq;
	freqTimeZero = t-oldfreq*(t-freqTimeZero)/newfreq;
    }

    void setResolution() {
	windowWidth = windowHeight = resBar.getValue();
	int border = windowWidth/9;
	if (border < 20)
	    border = 20;
	windowOffsetX = windowOffsetY = border;
	gridSizeX = windowWidth + windowOffsetX*2;
	gridSizeY = windowHeight + windowOffsetY*2;
	windowBottom = windowOffsetY+windowHeight-1;
	windowRight  = windowOffsetX+windowWidth -1;
    }

    void setResolution(int x) {
	resBar.setValue(x);
	setResolution();
	reinit();
    }

    public void mouseDragged(MouseEvent e) {
	if (view3dCheck.getState()) {
	    view3dDrag(e);
	}
	if (!dragging)
	    selectSource(e);
	dragging = true;
	edit(e);
	adjustResolution = false;
	cv.repaint(0);
    }
    public void mouseMoved(MouseEvent e) {
	if (dragging)
	    return;
	int x = e.getX();
	int y = e.getY();
	dragStartX = dragX = x; dragStartY = dragY = y;
	viewAngleDragStart = viewAngle;
	viewHeightDragStart = viewHeight;
	selectSource(e);
	if (stoppedCheck.getState())
	    cv.repaint(0);
    }

    void view3dDrag(MouseEvent e) {
	int x = e.getX(); int y = e.getY();
	viewAngle = (dragStartX-x)/40. + viewAngleDragStart;
	while (viewAngle < 0)
	    viewAngle += 2*pi;
	while (viewAngle >= 2*pi)
	    viewAngle -= 2*pi;
	viewAngleCos = Math.cos(viewAngle);
	viewAngleSin = Math.sin(viewAngle);
	viewHeight = (dragStartY-y)/10. + viewHeightDragStart;
	
	/*viewZoom = (y-dragStartY)/40. + viewZoomDragStart;
	if (viewZoom < .1)
	    viewZoom = .1;
	    System.out.println(viewZoom);*/
	cv.repaint();
    }
    
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
	dragStartX = -1;
    }
    public void mousePressed(MouseEvent e) {
	adjustResolution = false;
	mouseMoved(e);
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	dragging = true;
	edit(e);
    }
    public void mouseReleased(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	dragging = false;
	dragSet = dragClear = false;
	cv.repaint();
    }
    public void itemStateChanged(ItemEvent e) {
	if (e.getItemSelectable() == stoppedCheck) {
	    cv.repaint();
	    return;
	}
	if (e.getItemSelectable() == sourceChooser) {
	    if (sourceChooser.getSelectedIndex() != sourceIndex)
	setSources();
	}
	if (e.getItemSelectable() == setupChooser)
	    doSetup();
	if (e.getItemSelectable() == colorChooser)
	    doColor();
    }

    void doSetup() {
	t = 0;
	if (resBar.getValue() < 32)
	    setResolution(32);
	doBlank();
	doBlankWalls();
	// don't use previous source positions, use defaults
	sourceCount = -1;
	sourceChooser.select(SRC_1S1F);
	dampingBar.setValue(10);
	setFreqBar(5);
	setBrightness(10);
	auxBar.setValue(1);
	fixedEndsCheck.setState(true);
	setup = (Setup)
	    setupList.elementAt(setupChooser.getSelectedIndex());
	setup.select();
	setup.doSetupSources();
	calcExceptions();
	setDamping();
	//System.out.println("setup " + setupChooser.getSelectedIndex());
    }

    void setBrightness(int x) {
	double m = x / 5.;
	m = (Math.log(m) + 5.) * 100;
	brightnessBar.setValue((int) m);
    }
    
    void doColor() {
	int cn = colorChooser.getSelectedIndex();
	wallColor   = schemeColors[cn][0];
	posColor    = schemeColors[cn][1];
	negColor    = schemeColors[cn][2];
	zeroColor   = schemeColors[cn][3];
	posMedColor = schemeColors[cn][4];
	negMedColor = schemeColors[cn][5];
	medColor    = schemeColors[cn][6];
	sourceColor = schemeColors[cn][7];
    }

    void addDefaultColorScheme() {
	String schemes[] = {
	    "#808080 #00ffff #000000 #008080 #0000ff #000000 #000080 #ffffff",
	    "#808080 #00ff00 #ff0000 #000000 #00ffff #ff00ff #0000ff #0000ff",
	    "#800000 #00ffff #0000ff #000000 #80c8c8 #8080c8 #808080 #ffffff",
	    "#800000 #ffffff #000000 #808080 #0000ff #000000 #000080 #00ff00",
	    "#800000 #ffff00 #0000ff #000000 #ffff80 #8080ff #808080 #ffffff",
	    "#808080 #00ff00 #ff0000 #FFFFFF #00ffff #ff00ff #0000ff #0000ff",
	    "#FF0000 #00FF00 #0000FF #FFFF00 #00FFFF #FF00FF #FFFFFF #000000"
	};
	int i;
	
	for (i = 0; i != 7; i++)
	    decodeColorScheme(i, schemes[i]);
	//colorChooser.hide();
    }

    void decodeColorScheme(int cn, String s) {
	StringTokenizer st = new StringTokenizer(s);
	while (st.hasMoreTokens()) {
	    int i;
	    for (i = 0; i != 8; i++)
	schemeColors[cn][i] = Color.decode(st.nextToken());
	}
	colorChooser.add("Color Scheme " + (cn+1));
    }
    
    void addMedium() {
	int i, j;
	for (i = 0; i != gridSizeX; i++)
	    for (j = gridSizeY/2; j != gridSizeY; j++)
	medium[i+j*gw] = mediumMax;
    }

    void setSources() {
	sourceIndex = sourceChooser.getSelectedIndex();
	int oldSCount = sourceCount;
	boolean oldPlane = sourcePlane;
	sourceFreqCount = 1;
	sourcePlane = (sourceChooser.getSelectedIndex() >= SRC_1S1F_PLANE &&
	       sourceChooser.getSelectedIndex() <  SRC_6S1F);
	sourceMoving = false;
	sourceWaveform = SWF_SIN;
	sourceCount = 1;
	boolean phase = false;
	switch (sourceChooser.getSelectedIndex()) {
	case 0: sourceCount = 0; break;
	case 2: sourceFreqCount = 2; break;
	case 3: sourceCount = 2; break;
	case 4: sourceCount = 2; sourceFreqCount = 2; break;
	case 5: sourceCount = 3; break;
	case 6: sourceCount = 4; break;
	case 7: sourceWaveform = SWF_SQUARE; break;
	case 8: sourceWaveform = SWF_PULSE; break;
	case 9: sourceMoving = true; break;
	case 11: sourceFreqCount = 2; break;
	case 12: sourceCount = 2; break;
	case 13: sourceCount = sourceFreqCount = 2; break;
	case 14: sourceWaveform = SWF_PULSE; break;
	case 15: phase = true; break;
	case 16: sourceCount = 6; break;
	case 17: sourceCount = 8; break;
	case 18: sourceCount = 10; break;
	case 19: sourceCount = 12; break;
	case 20: sourceCount = 16; break;
	case 21: sourceCount = 20; break;
	}
	if (sourceFreqCount >= 2) {
	    auxFunction = AUX_FREQ;
	    auxBar.setValue(freqBar.getValue());
	    if (sourceCount == 2)
	auxLabel.setText("Source 2 Frequency");
	    else
	auxLabel.setText("2nd Frequency");
	} else if (sourceCount == 2 || sourceCount >= 4 || phase) {
	    auxFunction = AUX_PHASE;
	    auxBar.setValue(1);
	    auxLabel.setText("Phase Difference");
	} else if (sourceMoving) {
	    auxFunction = AUX_SPEED;
	    auxBar.setValue(7);
	    auxLabel.setText("Source Speed");
	} else {
	    auxFunction = AUX_NONE;
	    auxBar.hide();
	    auxLabel.hide();
	}
	if (auxFunction != AUX_NONE) {
	    auxBar.show();
	    auxLabel.show();
	}
	validate();
	
	if (sourcePlane) {
	    sourceCount *= 2;
	    if (!(oldPlane && oldSCount == sourceCount)) {
	int x2 = windowOffsetX+windowWidth-1;
	int y2 = windowOffsetY+windowHeight-1;
	sources[0] = new OscSource(windowOffsetX, windowOffsetY+1);
	sources[1] = new OscSource(x2, windowOffsetY+1);
	sources[2] = new OscSource(windowOffsetX, y2);
	sources[3] = new OscSource(x2, y2);
	    }
	} else if (!(oldSCount == sourceCount && !oldPlane)) {
	    sources[0] = new OscSource(gridSizeX/2, windowOffsetY+1);
	    sources[1] = new OscSource(gridSizeX/2, gridSizeY-windowOffsetY-2);
	    sources[2] = new OscSource(windowOffsetX+1, gridSizeY/2);
	    sources[3] = new OscSource(gridSizeX-windowOffsetX-2, gridSizeY/2);
	    int i;
	    for (i = 4; i < sourceCount; i++)
	sources[i] = new OscSource(windowOffsetX+1+i*2, gridSizeY/2);
	}
    }

    class OscSource {
	int x;
	int y;
	float v;
	OscSource(int xx, int yy) { x = xx; y = yy; }
	int getScreenX() {
	    return ((x-windowOffsetX) * winSize.width+winSize.width/2)
	/windowWidth;
	}
	int getScreenY() {
	    return ((y-windowOffsetY) * winSize.height+winSize.height/2)
	/windowHeight;
	}
    };

    void doImport() {
	if (impDialog != null) {
	    requestFocus();
	    impDialog.setVisible(false);
	    impDialog = null;
	}
	String dump = "";
	
	int i;
	dump = "$ 0 " + resBar.getValue() + " " +
	    sourceChooser.getSelectedIndex() + " " +
	    colorChooser.getSelectedIndex() + " " +
	    fixedEndsCheck.getState() + " " +
	    view3dCheck.getState() + " " +
	    speedBar.getValue() + " " +
	    freqBar.getValue() + " " +
	    brightnessBar.getValue() + " "+
	    auxBar.getValue() + "\n";
	for (i = 0; i != sourceCount; i++) {
	    OscSource src = sources[i];
	    dump += "s " + src.x + " " + src.y + "\n";
	}
	for (i = 0; i != gridSizeXY; ) {
	    if (i >= gridSizeX) {
	int istart = i;
	for (; i < gridSizeXY &&
	 walls [i] == walls [i-gridSizeX] &&
	 medium[i] == medium[i-gridSizeX]; i++)
	    ;
	if (i > istart) {
	    dump += "l " + (i-istart) + "\n";
	    continue;
	}
	    }
	    boolean x = walls[i];
	    int m = medium[i];
	    int ct = 0;
	    for (; i < gridSizeXY && walls[i] == x && medium[i] == m; ct++, i++)
	;
	    dump += (x ? "w " : "c ") + ct + " " + m + "\n";
	}
	impDialog = new ImportDialog(this, dump);
	impDialog.show();
    }

    void readImport(String s) {
	byte b[] = s.getBytes();
	int len = s.length();
	int p;
	int x = 0;
	int srci = 0;
	setupChooser.select(0);
	setup = (Setup) setupList.elementAt(0);
	for (p = 0; p < len; ) {
	    int l;
	    int linelen = 0;
	    for (l = 0; l != len-p; l++)
	if (b[l+p] == '\n' || b[l+p] == '\r') {
	    linelen = l++;
	    if (l+p < b.length && b[l+p] == '\n')
	l++;
	    break;
	}
	    String line = new String(b, p, linelen);
	    StringTokenizer st = new StringTokenizer(line);
	    while (st.hasMoreTokens()) {
	String type = st.nextToken();
	int tint = type.charAt(0);
	try {
	    if (tint == '$') {
	int flags = new Integer(st.nextToken()).intValue();
	
	resBar.setValue(new Integer(st.nextToken()).intValue());
	setResolution();
	reinit(false);
	
	sourceChooser.select(new Integer(st.nextToken()).intValue());
	setSources();
	
	colorChooser.select(new Integer(st.nextToken()).intValue());
	doColor();
	
	fixedEndsCheck.setState(st.nextToken().compareTo("true") == 0);
	view3dCheck.setState(st.nextToken().compareTo("true") == 0);
	speedBar.setValue(new Integer(st.nextToken()).intValue());
	freqBar.setValue(new Integer(st.nextToken()).intValue());
	brightnessBar.setValue(new Integer(st.nextToken()).intValue());
	auxBar.setValue(new Integer(st.nextToken()).intValue());
	break;
	    }
	    if (tint == 'w' || tint == 'c') {
	boolean w = (tint == 'w');
	int ct = new Integer(st.nextToken()).intValue();
	int md = new Integer(st.nextToken()).intValue();
	for (; ct > 0; ct--, x++) {
	    walls[x] = w;
	    medium[x] = md;
	}
	break;
	    }
	    if (tint == 'l') {
	int ct = new Integer(st.nextToken()).intValue();
	for (; ct > 0; ct--, x++) {
	    walls[x] = walls[x-gridSizeX];
	    medium[x] = medium[x-gridSizeX];
	}
	break;
	    }
	    if (tint == 's') {
	int sx = new Integer(st.nextToken()).intValue();
	int sy = new Integer(st.nextToken()).intValue();
	sources[srci].x = sx;
	sources[srci].y = sy;
	srci++;
	break;
	    }
	    System.out.println("unknown type!");
	} catch (Exception ee) {
	    ee.printStackTrace();
	    break;
	}
	break;
	    }
	    p += l;
	    
	}
	calcExceptions();
	setDamping();
    }
    
    class ImportDialogLayout implements LayoutManager {
	public ImportDialogLayout() {}
	public void addLayoutComponent(String name, Component c) {}
	public void removeLayoutComponent(Component c) {}
	public Dimension preferredLayoutSize(Container target) {
	    return new Dimension(500, 500);
	}
	public Dimension minimumLayoutSize(Container target) {
	    return new Dimension(100,100);
	}
	public void layoutContainer(Container target) {
	    Insets insets = target.insets();
	    int targetw = target.size().width - insets.left - insets.right;
	    int targeth = target.size().height - (insets.top+insets.bottom);
	    int i;
	    int pw = 300;
	    if (target.getComponentCount() == 0)
	return;
	    Component cl = target.getComponent(target.getComponentCount()-1);
	    Dimension dl = cl.getPreferredSize();
	    target.getComponent(0).move(insets.left, insets.top);
	    int cw = target.size().width - insets.left - insets.right;
	    int ch = target.size().height - insets.top - insets.bottom -
	dl.height;
	    target.getComponent(0).resize(cw, ch);
	    int h = ch + insets.top;
	    int x = 0;
	    for (i = 1; i < target.getComponentCount(); i++) {
	Component m = target.getComponent(i);
	if (m.isVisible()) {
	    Dimension d = m.getPreferredSize();
	    m.move(insets.left+x, h);
	    m.resize(d.width, d.height);
	    x += d.width;
	}
	    }
	}
    };

    class ImportDialog extends Dialog implements ActionListener {
	RippleFrame rframe;
	Button importButton, clearButton, closeButton;
	TextArea text;
	
	ImportDialog(RippleFrame f, String str) {
	    super(f, (str.length() > 0) ? "Export" : "Import", false);
	    rframe = f;
	    setLayout(new ImportDialogLayout());
	    add(text = new TextArea(str, 10, 60, TextArea.SCROLLBARS_BOTH));
	    add(importButton = new Button("Import"));
	    importButton.addActionListener(this);
	    add(clearButton = new Button("Clear"));
	    clearButton.addActionListener(this);
	    add(closeButton = new Button("Close"));
	    closeButton.addActionListener(this);
	    Point x = rframe.getLocationOnScreen();
	    resize(400, 300);
	    Dimension d = getSize();
	    setLocation(x.x + (winSize.width-d.width)/2,
	x.y + (winSize.height-d.height)/2);
	    show();
	    if (str.length() > 0)
	text.selectAll();
	}

	public void actionPerformed(ActionEvent e) {
	    int i;
	    Object src = e.getSource();
	    if (src == importButton) {
	rframe.readImport(text.getText());
	setVisible(false);
	    }
	    if (src == closeButton)
	setVisible(false);
	    if (src == clearButton)
	text.setText("");
	}
	
	public boolean handleEvent(Event ev) {
	    if (ev.id == Event.WINDOW_DESTROY) {
	rframe.requestFocus();
	setVisible(false);
	return true;
	    }
	    return super.handleEvent(ev);
	}
    }
    
    abstract class Setup {
	abstract String getName();
	abstract void select();
	void doSetupSources() { setSources(); }
	void deselect() {}
	double sourceStrength() { return 1; }
	abstract Setup createNext();
	void eachFrame() {}
	float calcSourcePhase(double ph, float v, double w) {
	    return v;
	}
    };

    class SingleSourceSetup extends Setup {
	String getName() { return "Single Source"; }
	void select() {
	    setFreqBar(15);
	    setBrightness(27);
	}
	Setup createNext() { return new DoubleSourceSetup(); }
    }
    class DoubleSourceSetup extends Setup {
	String getName() { return "Two Sources"; }
	void select() {
	    setFreqBar(15);
	    setBrightness(19);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_2S1F);
	    setSources();
	    sources[0].y = gridSizeY/2 - 8;
	    sources[1].y = gridSizeY/2 + 8;
	    sources[0].x = sources[1].x = gridSizeX/2;
	}
	Setup createNext() { return new QuadrupleSourceSetup(); }
    }
    class QuadrupleSourceSetup extends Setup {
	String getName() { return "Four Sources"; }
	void select() {
	    sourceChooser.select(SRC_4S1F);
	    setFreqBar(15);
	}
	Setup createNext() { return new SingleSlitSetup(); }
    }
    class SingleSlitSetup extends Setup {
	String getName() { return "Single Slit"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i;
	    int x = gridSizeX/2;
	    int y = windowOffsetY+8; // +4
	    for (i = 0; i != gridSizeX; i++)
	setWall(i, y);
	    for (i = -8; i <= 8; i++) // was 4
	setWall(x+i, y, false);
	    setBrightness(7);
	    setFreqBar(25);
	}
	Setup createNext() { return new DoubleSlitSetup(); }
    }
    class DoubleSlitSetup extends Setup {
	String getName() { return "Double Slit"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i;
	    int x = gridSizeX/2;
	    int y = windowOffsetY+4;
	    for (i = 0; i != gridSizeX; i++)
	setWall(i, y);
	    for (i = 0; i != 3; i++) {
	setWall(x-5-i, y, false);
	setWall(x+5+i, y, false);
	    }
	    brightnessBar.setValue(488);
	    setFreqBar(22);
	}
	Setup createNext() { return new TripleSlitSetup(); }
    }
    class TripleSlitSetup extends Setup {
	String getName() { return "Triple Slit"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i;
	    int x = gridSizeX/2;
	    int y = windowOffsetY+4;
	    for (i = 0; i != gridSizeX; i++)
	setWall(i, y);
	    for (i = -1; i <= 1; i++) {
	setWall(x-12+i, y, false);
	setWall(x+i, y, false);
	setWall(x+12+i, y, false);
	    }
	    setBrightness(12);
	    setFreqBar(22);
	}
	Setup createNext() { return new ObstacleSetup(); }
    }
    class ObstacleSetup extends Setup {
	String getName() { return "Obstacle"; }
	void select() {
	    int i;
	    int x = gridSizeX/2;
	    int y = windowOffsetY+12; // was +6
	    for (i = -15; i <= 15; i++) // was 5
	setWall(x+i, y);
	    setBrightness(280);
	    setFreqBar(20);
	}
	Setup createNext() { return new HalfPlaneSetup(); }
    }
    class HalfPlaneSetup extends Setup {
	String getName() { return "Half Plane"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int x = windowOffsetX+windowWidth/2;
	    int i;
	    for (i = windowWidth/2; i < windowWidth; i++)
	setWall(windowOffsetX+i, windowOffsetY+3);
	    setBrightness(4);
	    setFreqBar(25);
	}
	Setup createNext() { return new DipoleSourceSetup(); }
    }
    class DipoleSourceSetup extends Setup {
	String getName() { return "Dipole Source"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_2S1F);
	    setSources();
	    sources[0].y = sources[1].y = gridSizeY/2;
	    sources[0].x = gridSizeX/2 - 1;
	    sources[1].x = gridSizeX/2 + 1;
	    auxBar.setValue(29);
	    setFreqBar(13);
	}
	void select() {
	    setBrightness(33);
	}
	Setup createNext() { return new LateralQuadrupoleSetup(); }
    }
    class LateralQuadrupoleSetup extends Setup {
	String getName() { return "Lateral Quadrupole"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_4S1F);
	    setSources();
	    sources[0].y = sources[2].y = gridSizeY/2;
	    sources[0].x = gridSizeX/2 - 2; sources[2].x = gridSizeX/2 + 2;
	    sources[1].x = sources[3].x = gridSizeX/2;
	    sources[1].y = gridSizeY/2 - 2; sources[3].y = gridSizeY/2 + 2;
	    setFreqBar(13);
	    auxBar.setValue(29);
	}
	void select() {
	    setBrightness(33);
	}
	Setup createNext() { return new LinearQuadrupoleSetup(); }
    }
    class LinearQuadrupoleSetup extends Setup {
	String getName() { return "Linear Quadrupole"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_4S1F);
	    setSources();
	    sources[0].y = sources[1].y = sources[2].y = sources[3].y =
	gridSizeY/2;
	    sources[0].x = gridSizeX/2 - 3;
	    sources[2].x = gridSizeX/2 + 3;
	    sources[1].x = gridSizeX/2 + 1;
	    sources[3].x = gridSizeX/2 - 1;
	    auxBar.setValue(29);
	    setFreqBar(13);
	}
	void select() {
	    setBrightness(33);
	}
	Setup createNext() { return new HexapoleSetup(); }
    }
    class HexapoleSetup extends Setup {
	String getName() { return "Hexapole"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_6S1F);
	    setSources();
	    doMultipole(6, 4);
	    setFreqBar(22);
	    auxBar.setValue(29);
	}
	void doMultipole(int n, double dist) {
	    int i;
	    for (i = 0; i != n; i++) {
	double xx = Math.round(dist*Math.cos(2*pi*i/n));
	double yy = Math.round(dist*Math.sin(2*pi*i/n));
	sources[i].x = gridSizeX/2 + (int) xx;
	sources[i].y = gridSizeY/2 + (int) yy;
	    }
	}
	void select() {
	    brightnessBar.setValue(648);
	}
	Setup createNext() { return new OctupoleSetup(); }
    }
    class OctupoleSetup extends HexapoleSetup {
	String getName() { return "Octupole"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_8S1F);
	    setSources();
	    doMultipole(8, 4);
	    setFreqBar(22);
	    auxBar.setValue(29);
	}
	Setup createNext() { return new Multi12Setup(); }
    }
    /*class Multi10Setup extends HexapoleSetup {
	String getName() { return "10-Pole"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_10S1F);
	    setSources();
	    doMultipole(10, 6);
	    setFreqBar(22);
	    auxBar.setValue(29);
	}
	Setup createNext() { return new Multi12Setup(); }
	}*/
    class Multi12Setup extends HexapoleSetup {
	String getName() { return "12-Pole"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_12S1F);
	    setSources();
	    doMultipole(12, 6);
	    setFreqBar(22);
	    auxBar.setValue(29);
	}
	Setup createNext() { return new PlaneWaveSetup(); }
    }
    /*class Multi16Setup extends HexapoleSetup {
	String getName() { return "16-Pole"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_16S1F);
	    setSources();
	    doMultipole(16, 8);
	    setFreqBar(22);
	    auxBar.setValue(29);
	}
	Setup createNext() { return new Multi20Setup(); }
    }
    class Multi20Setup extends HexapoleSetup {
	String getName() { return "20-Pole"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_20S1F);
	    setSources();
	    doMultipole(20, 10);
	    setFreqBar(22);
	    auxBar.setValue(29);
	}
	Setup createNext() { return new PlaneWaveSetup(); }
	}*/
    class PlaneWaveSetup extends Setup {
	String getName() { return "Plane Wave"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    //setBrightness(7);
	    setFreqBar(15);
	}
	Setup createNext() { return new IntersectingPlaneWavesSetup(); }
    }
    class IntersectingPlaneWavesSetup extends Setup {
	String getName() { return "Intersecting Planes"; }
	void select() {
	    setBrightness(4);
	    setFreqBar(17);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_2S1F_PLANE);
	    setSources();
	    sources[0].y = sources[1].y = windowOffsetY;
	    sources[0].x = windowOffsetX+1;
	    sources[2].x = sources[3].x = windowOffsetX;
	    sources[2].y = windowOffsetY+1;
	    sources[3].y = windowOffsetY+windowHeight-1;
	}
	Setup createNext() { return new PhasedArray1Setup(); }
    }
    class PhasedArray1Setup extends Setup {
	String getName() { return "Phased Array 1"; }
	void select() {
	    setBrightness(5);
	    setFreqBar(17);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_1S1F_PLANE_PHASE);
	    setSources();
	    sources[0].x = sources[1].x = gridSizeX/2;
	    sources[0].y = gridSizeY/2-12;
	    sources[1].y = gridSizeY/2+12;
	    auxBar.setValue(5);
	}
	float calcSourcePhase(double ph, float v, double w) {
	    ph *= (auxBar.getValue()-15)*3.8*freqBar.getValue()*freqMult;
	    return (float) Math.sin(w+ph);
	}
	Setup createNext() { return new PhasedArray2Setup(); }
    }
    class PhasedArray2Setup extends PhasedArray1Setup {
	String getName() { return "Phased Array 2"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_1S1F_PLANE_PHASE);
	    setSources();
	    sources[0].x = sources[1].x = windowOffsetX+1;
	    sources[0].y = windowOffsetY+1;
	    sources[1].y = windowOffsetY+windowHeight-2;
	    auxBar.setValue(5);
	}
	float calcSourcePhase(double ph, float v, double w) {
	    double d = auxBar.getValue()*2.5/30.;
	    ph -= .5;
	    ph = Math.sqrt(ph*ph+d*d);
	    ph *= freqBar.getValue()*freqMult*108;
	    return (float) Math.sin(w+ph);
	}
	Setup createNext() { return new PhasedArray3Setup(); }
    }
    class PhasedArray3Setup extends PhasedArray2Setup {
	String getName() { return "Phased Array 3"; }
	float calcSourcePhase(double ph, float v, double w) {
	    double d = auxBar.getValue()*2.5/30.;
	    ph -= .5;
	    ph = Math.sqrt(ph*ph+d*d);
	    ph *= freqBar.getValue()*freqMult*108;
	    return (float) Math.sin(w-ph);
	}
	Setup createNext() { return new DopplerSetup(); }
    }
    class DopplerSetup extends Setup {
	String getName() { return "Doppler Effect 1"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_MOVING);
	    setFreqBar(13);
	    setBrightness(20);
	    fixedEndsCheck.setState(false);
	}
	Setup createNext() { return new Doppler2Setup(); }
    }
    class Doppler2Setup extends Setup {
	String getName() { return "Doppler Effect 2"; }
	double wall;
	int dir;
	int waiting;
	void select() {
	    wall = gridSizeY/2.;
	    dir = 1;
	    waiting = 0;
	    setFreqBar(13);
	    setBrightness(220);
	}
	void doSetupSources() {
	    sourceChooser.select(SRC_1S1F);
	    setSources();
	    sources[0].x = windowOffsetX+1;
	    sources[0].y = windowOffsetY+1;
	}
	void eachFrame() {
	    if (waiting > 0) {
	waiting--;
	return;
	    }
	    int w1 = (int) wall;
	    wall += dir*.04;
	    int w2 = (int) wall;
	    if (w1 != w2) {
	int i;
	for (i = windowOffsetX+windowWidth/3;
	     i <= gridSizeX-1; i++) {
	    setWall(i, w1, false);
	    setWall(i, w2);
	    int gi = i+w1*gw;
	    if (w2 < w1)
	func[gi] = funci[gi] = 0;
	    else if (w1 > 1) {
	func [gi] = func [gi-gw]/2;
	funci[gi] = funci[gi-gw]/2;
	    }
	}
	int w3 = (w2-windowOffsetY)/2 + windowOffsetY;
	for (i = windowOffsetY; i < w3; i++)
	    setWall(gridSizeX/2, i);
	setWall(gridSizeX/2, i, false);
	calcExceptions();
	    }
	    if (w2 == windowOffsetY+windowHeight/4 ||
	w2 == windowOffsetY+windowHeight*3/4) {
	dir = -dir;
	waiting = 1000;
	    }
	}
	Setup createNext() { return new SonicBoomSetup(); }
    }
    class SonicBoomSetup extends Setup {
	String getName() { return "Sonic Boom"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_MOVING);
	    setFreqBar(13);
	    setBrightness(20);
	    fixedEndsCheck.setState(false);
	}
	void doSetupSources() {
	    setSources();
	    auxBar.setValue(30);
	}
	Setup createNext() { return new BigModeSetup(); }
    }
    void setupMode(int x, int y, int sx, int sy, int nx, int ny) {
	int i, j;
	for (i = 0; i != sx; i++)
	    for (j = 0; j != sy; j++) {
	int gi = i+x+gw*(j+y);
	func [gi] = (float)
	    (Math.sin(pi*nx*(i+1)/(sx+1))*
	     Math.sin(pi*ny*(j+1)/(sy+1)));
	funci[gi] = 0;
	    }
    }
    void setupAcousticMode(int x, int y, int sx, int sy, int nx, int ny) {
	int i, j;
	if (nx == 0 && ny == 0)
	    return;
	for (i = 0; i != sx; i++)
	    for (j = 0; j != sy; j++) {
	int gi = i+x+gw*(j+y);
	func [gi] = (float)
	    (Math.cos(pi*nx*i/(sx-1))*
	     Math.cos(pi*ny*j/(sy-1)));
	funci[gi] = 0;
	    }
    }
    class BigModeSetup extends Setup {
	String getName() { return "Big 1x1 Mode"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i;
	    int n = windowWidth*3/4;
	    int x = windowOffsetX+windowWidth/2-n/2;
	    int y = windowOffsetY+windowHeight/2-n/2;
	    for (i = 0; i != n+2; i++) {
	setWall(x+i-1, y-1);
	setWall(x+i-1, y+n);
	setWall(x-1, y+i-1);
	setWall(x+n, y+i-1);
	    }
	    setupMode(x, y, n, n, 1, 1);
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new OneByOneModesSetup(); }
    }
    class OneByOneModesSetup extends Setup {
	String getName() { return "1x1 Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 5;
	    while (y + ny < windowHeight) {
	int nx = ((y+ny)*(windowWidth-8)/windowHeight)+6;
	int y1 = y + windowOffsetY;
	int x1 = windowOffsetX + 1;
	for (i = 0; i != nx+2; i++) {
	    setWall(x1+i-1, y1-1);
	    setWall(x1+i-1, y1+ny);
	}
	for (j = 0; j != ny+2; j++) {
	    setWall(x1-1, y1+j-1);
	    setWall(x1+nx, y1+j-1);
	}
	setupMode(x1, y1, nx, ny, 1, 1);
	y += ny+1;
	    }
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new OneByNModesSetup(); }
    }
    class OneByNModesSetup extends Setup {
	String getName() { return "1xN Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 5;
	    int nx = windowWidth-2;
	    int mode = 1;
	    while (y + ny < windowHeight) {
	int y1 = y + windowOffsetY;
	int x1 = windowOffsetX + 1;
	for (i = 0; i != nx+2; i++) {
	    setWall(x1+i-1, y1-1);
	    setWall(x1+i-1, y1+ny);
	}
	for (j = 0; j != ny+2; j++) {
	    setWall(x1-1, y1+j-1);
	    setWall(x1+nx, y1+j-1);
	}
	setupMode(x1, y1, nx, ny, mode, 1);
	y += ny+1;
	mode++;
	    }
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new NByNModesSetup(); }
    }
    class NByNModesSetup extends Setup {
	String getName() { return "NxN Modes"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int modex, modey;
	    int maxmode = 3;
	    if (resBar.getValue() >= 70)
	maxmode++;
	    if (resBar.getValue() >= 100)
	maxmode++;
	    int ny = windowHeight/maxmode-2;
	    int nx = windowWidth/maxmode-2;
	    for (modex = 1; modex <= maxmode; modex++)
	for (modey = 1; modey <= maxmode; modey++) {
	    int x1 = windowOffsetX + 1 + (ny+1)*(modey-1);
	    int y1 = windowOffsetY + 1 + (nx+1)*(modex-1);
	    for (i = 0; i != nx+2; i++) {
	setWall(x1+i-1, y1-1);
	setWall(x1+i-1, y1+ny);
	    }
	    for (j = 0; j != ny+2; j++) {
	setWall(x1-1, y1+j-1);
	setWall(x1+nx, y1+j-1);
	    }
	    setupMode(x1, y1, nx, ny, modex, modey);
	}
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new OneByNModeCombosSetup(); }
    }
    class OneByNModeCombosSetup extends Setup {
	String getName() { return "1xN Mode Combos"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 5;
	    int nx = windowWidth-2;
	    while (y + ny < windowHeight) {
	int mode1 = getrand(12)+1;
	int mode2;
	do
	    mode2 = getrand(12)+1;
	while (mode1 == mode2);
	int y1 = y + windowOffsetY;
	int x1 = windowOffsetX + 1;
	for (i = 0; i != nx+2; i++) {
	    setWall(x1+i-1, y1-1);
	    setWall(x1+i-1, y1+ny);
	}
	for (j = 0; j != ny+2; j++) {
	    setWall(x1-1, y1+j-1);
	    setWall(x1+nx, y1+j-1);
	}
	for (i = 0; i != nx; i++)
	    for (j = 0; j != ny; j++) {
	int gi = i+x1+gw*(j+y1);
	func [gi] = (float)
	    (Math.sin(mode1*pi*(i+1)/(nx+1))*
	     Math.sin(pi*(j+1)/(ny+1))*.5 +
	     Math.sin(mode2*pi*(i+1)/(nx+1))*
	     Math.sin(pi*(j+1)/(ny+1))*.5);
	funci[gi] = 0;
	    }
	y += ny+1;
	    }
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new NByNModeCombosSetup(); }
    }
    class NByNModeCombosSetup extends Setup {
	String getName() { return "NxN Mode Combos"; }
	void select() {
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int maxmode = 3;
	    if (resBar.getValue() >= 70)
	maxmode++;
	    if (resBar.getValue() >= 100)
	maxmode++;
	    int ny = windowHeight/maxmode-2;
	    int nx = windowWidth/maxmode-2;
	    int gx, gy;
	    for (gx = 1; gx <= maxmode; gx++)
	for (gy = 1; gy <= maxmode; gy++) {
	    int mode1x = getrand(4)+1;
	    int mode1y = getrand(4)+1;
	    int mode2x, mode2y;
	    do {
	mode2x = getrand(4)+1;
	mode2y = getrand(4)+1;
	    } while (mode1x == mode2x && mode1y == mode2y);
	    int x1 = windowOffsetX + 1 + (ny+1)*(gx-1);
	    int y1 = windowOffsetY + 1 + (nx+1)*(gy-1);
	    for (i = 0; i != nx+2; i++) {
	setWall(x1+i-1, y1-1);
	setWall(x1+i-1, y1+ny);
	    }
	    for (j = 0; j != ny+2; j++) {
	setWall(x1-1, y1+j-1);
	setWall(x1+nx, y1+j-1);
	    }
	    for (i = 0; i != nx; i++)
	for (j = 0; j != ny; j++) {
	    int gi = i+x1+gw*(j+y1);
	    func[gi] = (float)
	(Math.sin(mode1x*pi*(i+1)/(nx+1))*
	 Math.sin(mode1y*pi*(j+1)/(ny+1))*.5 +
	 Math.sin(mode2x*pi*(i+1)/(nx+1))*
	 Math.sin(mode2y*pi*(j+1)/(ny+1))*.5);
	    funci[gi] = 0;
	}
	}
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new ZeroByOneModesSetup(); }
    }
    class ZeroByOneModesSetup extends Setup {
	String getName() { return "0x1 Acoustic Modes"; }
	void select() {
	    fixedEndsCheck.setState(false);
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 5;
	    while (y + ny < windowHeight) {
	int nx = ((y+ny)*(windowWidth-8)/windowHeight)+6;
	int y1 = y + windowOffsetY;
	int x1 = windowOffsetX + 1;
	for (i = 0; i != nx+2; i++) {
	    setWall(x1+i-1, y1-1);
	    setWall(x1+i-1, y1+ny);
	}
	for (j = 0; j != ny+2; j++) {
	    setWall(x1-1, y1+j-1);
	    setWall(x1+nx, y1+j-1);
	}
	setupAcousticMode(x1, y1, nx, ny, 1, 0);
	y += ny+1;
	    }
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new ZeroByNModesSetup(); }
    }
    class ZeroByNModesSetup extends Setup {
	String getName() { return "0xN Acoustic Modes"; }
	void select() {
	    fixedEndsCheck.setState(false);
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 5;
	    int nx = windowWidth-2;
	    int mode = 1;
	    while (y + ny < windowHeight) {
	int y1 = y + windowOffsetY;
	int x1 = windowOffsetX + 1;
	for (i = 0; i != nx+2; i++) {
	    setWall(x1+i-1, y1-1);
	    setWall(x1+i-1, y1+ny);
	}
	for (j = 0; j != ny+2; j++) {
	    setWall(x1-1, y1+j-1);
	    setWall(x1+nx, y1+j-1);
	}
	setupAcousticMode(x1, y1, nx, ny, mode, 0);
	y += ny+1;
	mode++;
	    }
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new NByNAcoModesSetup(); }
    }
    class NByNAcoModesSetup extends Setup {
	String getName() { return "NxN Acoustic Modes"; }
	void select() {
	    fixedEndsCheck.setState(false);
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int modex, modey;
	    int maxmode = 2;
	    if (resBar.getValue() >= 70)
	maxmode++;
	    // weird things start to happen if maxmode goes higher than 4
	    int ny = windowHeight/(maxmode+1)-2;
	    int nx = windowWidth/(maxmode+1)-2;
	    for (modex = 0; modex <= maxmode; modex++)
	for (modey = 0; modey <= maxmode; modey++) {
	    int x1 = windowOffsetX + 1 + (ny+1)*(modey);
	    int y1 = windowOffsetY + 1 + (nx+1)*(modex);
	    for (i = 0; i != nx+2; i++) {
	setWall(x1+i-1, y1-1);
	setWall(x1+i-1, y1+ny);
	    }
	    for (j = 0; j != ny+2; j++) {
	setWall(x1-1, y1+j-1);
	setWall(x1+nx, y1+j-1);
	    }
	    setupAcousticMode(x1, y1, nx, ny, modex, modey);
	}
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new CoupledCavitiesSetup(); }
    }
    class CoupledCavitiesSetup extends Setup {
	String getName() { return "Coupled Cavities"; }
	void select() {
	    fixedEndsCheck.setState(false);
	    sourceChooser.select(SRC_NONE);
	    int i, j;
	    int y = 1;
	    int ny = 5;
	    while (y + ny < windowHeight) {
	int nx = 35;
	int y1 = y + windowOffsetY;
	int x1 = windowOffsetX + 1;
	for (i = 0; i != nx+2; i++) {
	    setWall(x1+i-1, y1-1);
	    setWall(x1+i-1, y1+ny);
	}
	for (j = 0; j != ny+2; j++) {
	    setWall(x1-1, y1+j-1);
	    setWall(x1+nx, y1+j-1);
	}
	for (j = 0; j != 2; j++) {
	    setWall(x1+nx/2, y1+j);
	    setWall(x1+nx/2, y1+4-j);
	}
	setupAcousticMode(x1, y1, nx/2, ny, 1, 0);
	y += ny+3;
	    }
	    dampingBar.setValue(1);
	}
	Setup createNext() { return new BeatsSetup(); }
    }
    class BeatsSetup extends Setup {
	String getName() { return "Beats"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_2S2F);
	    setSources();
	    auxBar.setValue(24);
	    sources[0].y = sources[1].y = gridSizeY/2;
	    sources[0].x = gridSizeX/2 - 2;
	    sources[1].x = gridSizeX/2 + 2;
	}
	void select() {
	    setBrightness(25);
	    setFreqBar(18);
	}
	Setup createNext() { return new SlowMediumSetup(); }
    }
    class SlowMediumSetup extends Setup {
	String getName() { return "Slow Medium"; }
	void select() {
	    addMedium();
	    setFreqBar(10);
	    setBrightness(33);
	}
	Setup createNext() { return new RefractionSetup(); }
    }
    class RefractionSetup extends Setup {
	String getName() { return "Refraction"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_1S1F_PLANE_PULSE);
	    setSources();
	    sources[0].x = windowOffsetX;
	    sources[0].y = windowOffsetY + windowHeight/4;
	    sources[1].x = windowOffsetX + windowWidth/3;
	    sources[1].y = windowOffsetY;
	    addMedium();
	    setFreqBar(1);
	    setBrightness(33);
	}
	void select() {}
	Setup createNext() { return new InternalReflectionSetup(); }
    }
    class InternalReflectionSetup extends Setup {
	String getName() { return "Internal Reflection"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_1S1F_PLANE_PULSE);
	    setSources();
	    sources[0].x = windowOffsetX;
	    sources[0].y = windowOffsetY + windowHeight*2/3;
	    sources[1].x = windowOffsetX + windowWidth/3;
	    sources[1].y = windowOffsetY + windowHeight-1;
	    addMedium();
	    setFreqBar(1);
	    setBrightness(33);
	}
	void select() {
	}
	Setup createNext() { return new CoatingSetup(); }
    }
    class CoatingSetup extends Setup {
	String getName() { return "Anti-Reflective Coating"; }
	void select() {
	    sourceChooser.select(SRC_1S1F);
	    addMedium();
	    int i, j;
	    // v2/c2 = 1-(mediumMaxIndex/mediumMax)*medium);
	    // n = sqrt(v2/c2)
	    double nmax = Math.sqrt(1-mediumMaxIndex);
	    double nroot = Math.sqrt(nmax);
	    int mm = (int) ((1-nmax)*mediumMax/mediumMaxIndex);
	    for (i = 0; i != gridSizeX; i++)
	for (j = gridSizeY/2-4; j != gridSizeY/2; j++)
	    medium[i+j*gw] = mm;
	    setFreqBar(6);
	    setBrightness(28);
	}
	Setup createNext() { return new ZonePlateEvenSetup(); }
    }
    class ZonePlateEvenSetup extends Setup {
	int zoneq;
	ZonePlateEvenSetup() { zoneq = 1; }
	String getName() { return "Zone Plate (Even)"; }
	void doSetupSources() { }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    setSources();
	    if (resBar.getValue() < 42)
	setResolution(42);
	    int i;
	    // wavelength by default = 25, we want it to be 6
	    int freq = 30;
	    setFreqBar(freq);
	    double halfwave = 25./(freq*2/5);
	    int y = sources[0].y+1;
	    int dy = windowOffsetY+windowHeight/2-y;
	    int dy2 = dy*dy;
	    int cx = gridSizeX/2;
	    for (i = 0; i != windowWidth; i++) {
	int x = windowOffsetX+i;
	int dx = cx-x;
	double dist = Math.sqrt(dx*dx+dy*dy);
	dist = (dist-dy);
	int zone = (int) (dist / halfwave);
	setWall(x, y, ((zone & 1) == zoneq));
	setWall(windowOffsetX, y);
	setWall(windowOffsetX+windowWidth-1, y);
	    }
	    setBrightness(zoneq == 1 ? 4 : 7);
	}
	Setup createNext() { return new ZonePlateOddSetup(); }
    }
    class ZonePlateOddSetup extends ZonePlateEvenSetup {
	ZonePlateOddSetup() { zoneq = 0; }
	String getName() { return "Zone Plate (Odd)"; }
	Setup createNext() { return new CircleSetup(); }
    }
    class CircleSetup extends Setup {
	CircleSetup() { circle = true; }
	boolean circle;
	String getName() { return "Circle"; }
	void doSetupSources() { }
	void select() {
	    int i;
	    int dx = windowWidth/2-2;
	    double a2 = dx*dx;
	    double b2 = a2/2;
	    if (circle)
	b2 = a2;
	    int cx = windowWidth/2 + windowOffsetX;
	    int cy = windowHeight/2 + windowOffsetY;
	    int ly = -1;
	    for (i = 0; i <= dx; i++) {
	double y = Math.sqrt((1-i*i/a2)*b2);
	int yi = (int) (y+1.5);
	if (i == dx)
	    yi = 0;
	if (ly == -1)
	    ly = yi;
	for (; ly >= yi; ly--) {
	    setWall(cx+i, cy+ly);
	    setWall(cx-i, cy+ly);
	    setWall(cx+i, cy-ly);
	    setWall(cx-i, cy-ly);
	    //setWall(cx-ly, cx+i);
	    //setWall(cx+ly, cx+i);
	}
	ly = yi;
	    }
	    int c = (int) (Math.sqrt(a2-b2));
	    //walls[cx+c][cy] = walls[cx-c][cy] = true;
	    //walls[cx][cy+c] = true;
	    sourceChooser.select(SRC_1S1F_PULSE);
	    setSources();
	    sources[0].x = cx-c;
	    sources[0].y = cy;
	    setFreqBar(1);
	    setBrightness(16);
	}
	Setup createNext() { return new EllipseSetup(); }
    }
    class EllipseSetup extends CircleSetup {
	EllipseSetup() { circle = false; }
	String getName() { return "Ellipse"; }
	Setup createNext() { return new ResonantCavitiesSetup(); }
    }
    class ResonantCavitiesSetup extends Setup {
	String getName() { return "Resonant Cavities 1"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 5;
	    int y1 = windowOffsetY + 11;
	    while (x + nx < windowWidth) {
	int ny = ((x+nx)*(windowHeight-18)/windowWidth)+6;
	int x1 = x + windowOffsetX;
	for (i = 0; i != ny+2; i++) {
	    setWall(x1-1, y1+i-1);
	    setWall(x1+nx, y1+i-1);
	}
	for (j = 0; j != nx+2; j++) {
	    setWall(x1+j-1, y1-1);
	    setWall(x1+j-1, y1+ny);
	}
	setWall(x1+nx/2, y1-1, false);
	x += nx+1;
	    }
	    for (; x < windowWidth; x++)
	setWall(x+windowOffsetX, y1-1);
	    setBrightness(30);
	    setFreqBar(15);
	}
	double sourceStrength() { return .1; }
	Setup createNext() { return new ResonantCavities2Setup(); }
    }
    class ResonantCavities2Setup extends Setup {
	String getName() { return "Resonant Cavities 2"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 5;
	    int y1 = windowOffsetY + 11;
	    int ny = 5;
	    while (x + nx < windowWidth) {
	int x1 = x + windowOffsetX;
	for (i = 0; i != ny+2; i++) {
	    setWall(x1-1, y1+i-1);
	    setWall(x1+nx, y1+i-1);
	}
	for (j = 0; j != nx+2; j++)
	    setWall(x1+j-1, y1+ny);
	x += nx+1;
	ny++;
	    }
	    for (; x < windowWidth; x++)
	setWall(x+windowOffsetX, y1-1);
	    setBrightness(30);
	    setFreqBar(16);
	}
	double sourceStrength() { return .03; }
	Setup createNext() { return new RoomResonanceSetup(); }
    }
    class RoomResonanceSetup extends Setup {
	String getName() { return "Room Resonance"; }
	void select() {
	    sourceChooser.select(SRC_4S1F);
	    setSources();
	    int i, j;
	    int modex, modey;
	    int ny = 17;
	    int nx = 17;
	    for (modex = 1; modex <= 2; modex++)
	for (modey = 1; modey <= 2; modey++) {
	    int x1 = windowOffsetX + 1 + (ny+1)*(modey-1);
	    int y1 = windowOffsetY + 1 + (nx+1)*(modex-1);
	    for (i = 0; i != nx+2; i++) {
	setWall(x1+i-1, y1-1);
	setWall(x1+i-1, y1+ny);
	    }
	    for (j = 0; j != ny+2; j++) {
	setWall(x1-1, y1+j-1);
	setWall(x1+nx, y1+j-1);
	    }
	}
	    sources[0].x = sources[2].x = windowOffsetX + 2;
	    sources[0].y = sources[1].y = windowOffsetY + 2;
	    sources[1].x = sources[3].x = windowOffsetX+1+nx+(nx+1)/2;
	    sources[2].y = sources[3].y = windowOffsetY+1+ny+(ny+1)/2;
	    fixedEndsCheck.setState(false);
	    dampingBar.setValue(10);
	    setBrightness(3);
	}
	void doSetupSources() { }
	Setup createNext() { return new Waveguides1Setup(); }
    }
    class Waveguides1Setup extends Setup {
	String getName() { return "Waveguides 1"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 3;
	    int y1 = windowOffsetY + 3;
	    int ny = windowHeight-2;
	    while (x + nx < windowWidth) {
	int x1 = x + windowOffsetX;
	for (i = 0; i != ny; i++) {
	    setWall(x1-1, y1+i-1);
	    setWall(x1+nx, y1+i-1);
	}
	nx++;
	x += nx;
	    }
	    for (; x < windowWidth; x++)
	setWall(x+windowOffsetX, y1-1);
	    setBrightness(6);
	    setFreqBar(14);
	}
	Setup createNext() { return new Waveguides2Setup(); }
    }
    class Waveguides2Setup extends Waveguides1Setup {
	String getName() { return "Waveguides 2"; }
	void select() {
	    super.select();
	    setFreqBar(8);
	}
	Setup createNext() { return new Waveguides3Setup(); }
    }
    class Waveguides3Setup extends Setup {
	String getName() { return "Waveguides 3"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int x = 1;
	    int nx = 8;
	    int y1 = windowOffsetY + 3;
	    int ny = windowHeight-2;
	    for (x = 1; x < windowWidth; x++)
	setWall(x+windowOffsetX, y1-1);
	    x = 1;
	    j = 0;
	    while (x + nx < windowWidth && j < nx) {
	int x1 = x + windowOffsetX;
	for (i = 0; i != ny; i++) {
	    setWall(x1-1, y1+i-1);
	    setWall(x1+nx, y1+i-1);
	}
	setWall(x1+j++, y1-1, false);
	x += nx+1;
	    }
	    setBrightness(89);
	    setFreqBar(16);
	}
	Setup createNext() { return new Waveguides4Setup(); }
    }
    class Waveguides4Setup extends Waveguides3Setup {
	String getName() { return "Waveguides 4"; }
	void select() {
	    super.select();
	    setBrightness(29);
	    setFreqBar(20);
	    fixedEndsCheck.setState(false);
	}
	Setup createNext() { return new Waveguides5Setup(); }
    }
    class Waveguides5Setup extends Waveguides3Setup {
	String getName() { return "Waveguides 5"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i;
	    int x = 1;
	    int nx = 8;
	    int y1 = windowOffsetY + 2;
	    int ny = windowHeight-1;
	    x = 1;
	    while (x + nx < windowWidth) {
	int x1 = x + windowOffsetX;
	for (i = 0; i != ny; i++) {
	    setWall(x1-1, y1+i-1);
	    setWall(x1+nx, y1+i-1);
	}
	x += nx+1;
	    }
	    setBrightness(9);
	    setFreqBar(22);
	}
	void eachFrame() {
	    int y = windowOffsetY+1;
	    int nx = 8;
	    int x = 1;
	    int g = 1;
	    while (x + nx < windowWidth) {
	int x1 = x + windowOffsetX;
	int j;
	int n1 = 1;
	int n2 = 1;
	switch (g) {
	case 1: n1 = n2 = 1; break;
	case 2: n1 = n2 = 2; break;
	case 3: n1 = 1; n2 = 2; break;
 	case 4: n1 = n2 = 3; break;
	case 5: n1 = 1; n2 = 3; break;
	case 6: n1 = 2; n2 = 3; break;
	default: n1 = n2 = 0; break;
	}
	for (j = 0; j != nx; j++)
	    func[x1+j+gw*y] *= .5*
	(Math.sin(pi*n1*(j+1)/(nx+1)) +
	 Math.sin(pi*n2*(j+1)/(nx+1)));
	x += nx+1;
	g++;
	    }
	}
	Setup createNext() { return new ParabolicMirror1Setup(); }
    }
    /*class HornSetup extends Setup {
	String getName() { return "Horn"; }
	void select() {
	    if (resBar.getValue() < 76)
	setResolution(76);
	    fixedEndsCheck.setState(false);
	    setFreqBar(3);
	    int i;
	    int cx = windowOffsetX+windowWidth/2;
	    int yy = windowHeight/2;
	    int oj = 0;
	    double lmult = Math.log(windowWidth/2-2)*1.2;
	    System.out.println(yy + " " + lmult);
	    for (i = 0; i < yy; i++) {
	int j = (int) (Math.exp(i*lmult/yy));
	System.out.println(i + " " +j);
	//int j = i*((windowWidth-5)/2)/yy;
	while (oj <= j) {
	    walls[cx+oj][windowOffsetY+i] =
	walls[cx-oj][windowOffsetY+i] = true;
	    oj++;
	}
	oj = j;
	    }
	    setBrightness(12);
	}
	Setup createNext() { return new ParabolicMirror1Setup(); }
	}*/
    class ParabolicMirror1Setup extends Setup {
	String getName() { return "Parabolic Mirror 1"; }
	void select() {
	    if (resBar.getValue() < 50)
	setResolution(50);
	    int i;
	    int cx = windowWidth/2 + windowOffsetX;
	    int lx = 0;
	    int dy = windowHeight/2;
	    int cy = windowHeight+windowOffsetY-2;
	    int dx = windowWidth/2-2;
	    double c = dx*dx*.5/dy;
	    if (c > 20)
	c = 20;
	    for (i = 0; i <= dy; i++) {
	double x = Math.sqrt(2*c*i);
	int xi = (int) (x+1.5);
	for (; lx <= xi; lx++) {
	    setWall(cx-lx, cy-i);
	    setWall(cx+lx, cy-i);
	}
	lx = xi;
	    }
	    setSources();
	    sources[0].x = cx;
	    sources[0].y = (int) (cy-1-c/2);
	    setBrightness(18);
	}
	void doSetupSources() {}
	Setup createNext() { return new ParabolicMirror2Setup(); }
    }
    class ParabolicMirror2Setup extends ParabolicMirror1Setup {
	String getName() { return "Parabolic Mirror 2"; }
	void doSetupSources() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    brightnessBar.setValue(370);
	    setFreqBar(15);
	    setSources();
	}
	Setup createNext() { return new SoundDuctSetup(); }
    }
    class SoundDuctSetup extends Setup {
	String getName() { return "Sound Duct"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PULSE);
	    fixedEndsCheck.setState(false);
	    int i;
	    int cx = windowOffsetX+windowWidth/2;
	    for (i = 0; i != windowHeight-12; i++) {
	setWall(cx-3, i+windowOffsetY+6);
	setWall(cx+3, i+windowOffsetY+6);
	    }
	    setFreqBar(1);
	    setBrightness(60);
	}
	Setup createNext() { return new BaffledPistonSetup(); }
    }
    class BaffledPistonSetup extends Setup {
	String getName() { return "Baffled Piston"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    fixedEndsCheck.setState(false);
	    int i;
	    for (i = 0; i != gridSizeY; i++)
	setWall(windowOffsetX+2, i);
	    for (i = 0; i <= 11; i++) {
	setWall(windowOffsetX, i+gridSizeY/2-5);
	if (i != 0 && i != 11)
	    setWall(windowOffsetX+2, i+gridSizeY/2-5, false);
	    }
	    setWall(windowOffsetX+1, gridSizeY/2-5);
	    setWall(windowOffsetX+1, gridSizeY/2+6);
	    setFreqBar(24);
	    setSources();
	    sources[0].x = sources[1].x = windowOffsetX+1;
	    sources[0].y = gridSizeY/2-4;
	    sources[1].y = gridSizeY/2+5;
	    setBrightness(18);
	}
	void doSetupSources() {
	}
	Setup createNext() { return new LowPassFilter1Setup(); }
    }
    class LowPassFilter1Setup extends Setup {
	String getName() { return "Low-Pass Filter 1"; }
	void select() {
	    if (resBar.getValue() < 43)
	setResolution(43);
	    fixedEndsCheck.setState(false);
	    int i, j;
	    for (i = 0; i != windowWidth; i++)
	setWall(i+windowOffsetX, windowOffsetY+9);
	    int cx = gridSizeX/2;
	    for (i = 1; i <= 4; i++)
	for (j = -7; j <= 7; j++)
	    setWall(cx+j, windowOffsetY+9*i);
	    for (i = 0; i <= 4; i++)
	for (j = -4; j <= 4; j++)
	    setWall(cx+j, windowOffsetY+9*i, false);
	    for (i = 0; i != 27; i++) {
	setWall(cx+7, windowOffsetY+9+i);
	setWall(cx-7, windowOffsetY+9+i);
	    }
	    setBrightness(38);
	}
	Setup createNext() { return new LowPassFilter2Setup(); }
    }
    class LowPassFilter2Setup extends LowPassFilter1Setup {
	String getName() { return "Low-Pass Filter 2"; }
	void select() {
	    super.select();
	    setFreqBar(17);
	}
	Setup createNext() { return new HighPassFilter1Setup(); }
    }
    class HighPassFilter1Setup extends Setup {
	String getName() { return "High-Pass Filter 1"; }
	void select() {
	    if (resBar.getValue() < 43)
	setResolution(43);
	    fixedEndsCheck.setState(false);
	    int i, j;
	    for (i = 0; i != windowWidth; i++)
	for (j = 0; j <= 25; j += 5)
	    setWall(i+windowOffsetX, windowOffsetY+9+j);
	    int cx = gridSizeX/2;
	    for (i = 0; i <= 25; i += 5) 
	for (j = -4; j <= 4; j++)
	    setWall(cx+j, windowOffsetY+9+i, false);
	    setBrightness(62);
	    // by default we show a freq high enough to be passed
	    setFreqBar(17);
	}
	Setup createNext() { return new HighPassFilter2Setup(); }
    }
    class HighPassFilter2Setup extends HighPassFilter1Setup {
	String getName() { return "High-Pass Filter 2"; }
	void select() {
	    super.select();
	    setFreqBar(7);
	}
	Setup createNext() { return new BandStopFilter1Setup(); }
    }
    class BandStopFilter1Setup extends Setup {
	String getName() { return "Band-Stop Filter 1"; }
	void select() {
	    if (resBar.getValue() < 43)
	setResolution(43);
	    fixedEndsCheck.setState(false);
	    int i, j, k;
	    for (i = 0; i != windowWidth; i++)
	setWall(i+windowOffsetX, windowOffsetY+9);
	    int cx = gridSizeX/2;
	    for (i = 1; i <= 2; i++)
	for (j = -11; j <= 11; j++) {
	    if (j > -5 && j < 5)
	continue;
	    setWall(cx+j, windowOffsetY+9+9*i);
	}
	    for (i = 0; i <= 1; i++)
	for (j = -4; j <= 4; j++)
	    setWall(cx+j, windowOffsetY+9+i*26, false);
	    for (i = 0; i <= 18; i++) {
	setWall(cx+11, windowOffsetY+9+i);
	setWall(cx-11, windowOffsetY+9+i);
	    }
	    for (i = 0; i != 3; i++)
	for (j = 0; j != 3; j++)
	    for (k = 9; k <= 18; k += 9) {
	setWall(cx+5+i, windowOffsetY+k+j);
	setWall(cx+5+i, windowOffsetY+9+k-j);
	setWall(cx-5-i, windowOffsetY+k+j);
	setWall(cx-5-i, windowOffsetY+9+k-j);
	    }
	    setBrightness(38);
	    setFreqBar(2);
	}
	Setup createNext() { return new BandStopFilter2Setup(); }
    }
    class BandStopFilter2Setup extends BandStopFilter1Setup {
	String getName() { return "Band-Stop Filter 2"; }
	void select() {
	    super.select();
	    setFreqBar(10);
	}
	Setup createNext() { return new BandStopFilter3Setup(); }
    }
    class BandStopFilter3Setup extends BandStopFilter1Setup {
	String getName() { return "Band-Stop Filter 3"; }
	void select() {
	    super.select();
	    // at this frequency it doesn't pass
	    setFreqBar(4);
	}
	Setup createNext() { return new PlanarConvexLensSetup(); }
    }
    class PlanarConvexLensSetup extends Setup {
	String getName() { return "Planar Convex Lens"; }
	void select() {
	    if (resBar.getValue() < 42)
	setResolution(42);
	    sourceChooser.select(SRC_1S1F_PLANE);
	    // need small wavelengths here to remove diffraction effects
	    int i, j;
	    int cx = gridSizeX/2;
	    int cy = windowHeight/8+windowOffsetY;
	    int x0 = windowWidth/3-2;
	    int y0 = 5;
	    double r = (.75*windowHeight)*.5;
	    double h = r-y0;
	    double r2 = r*r;
	    if (x0 > r)
	x0 = (int) r;
	    for (i = 0; i <= x0; i++) {
	int y = 2+(int) (Math.sqrt(r2-i*i)-h+.5);
	for (; y >= 0; y--) {
	    setMedium(cx+i, cy+y, mediumMax/2);
	    setMedium(cx-i, cy+y, mediumMax/2);
	}
	    }
	    setFreqBar(19);
	    setBrightness(6);
	}
	Setup createNext() { return new BiconvexLensSetup(); }
    }
    class BiconvexLensSetup extends Setup {
	String getName() { return "Biconvex Lens"; }
	void select() {
	    if (resBar.getValue() < 50)
	setResolution(50);
	    setSources();
	    int i, j;
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    int x0 = windowWidth/3-2;
	    int y0 = 10;
	    double r = (.75*.5*windowHeight)*.5;
	    double h = r-y0;
	    double r2 = r*r;
	    if (x0 > r)
	x0 = (int) r;
	    for (i = 0; i <= x0; i++) {
	int y = 1+(int) (Math.sqrt(r2-i*i)-h+.5);
	for (; y >= 0; y--) {
	    setMedium(cx+i, cy+y, mediumMax/2);
	    setMedium(cx-i, cy+y, mediumMax/2);
	    setMedium(cx+i, cy-y, mediumMax/2);
	    setMedium(cx-i, cy-y, mediumMax/2);
	}
	    }
	    setFreqBar(19);
	    setBrightness(66);
	    sources[0].y = cy-(2+2*(int) r);
	}
	void doSetupSources() {}
	Setup createNext() { return new PlanarConcaveSetup(); }
    }
    class PlanarConcaveSetup extends Setup {
	String getName() { return "Planar Concave Lens"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int cx = gridSizeX/2;
	    int cy = windowHeight/8+windowOffsetY;
	    int x0 = windowWidth/5;
	    int y0 = 5;
	    double r = (.25*windowHeight)*.5;
	    double h = r-y0;
	    double r2 = r*r;
	    if (x0 > r)
	x0 = (int) r;
	    for (i = 0; i <= x0; i++) {
	int y = y0+2-(int) (Math.sqrt(r2-i*i)-h+.5);
	for (; y >= 0; y--) {
	    setMedium(cx+i, cy+y, mediumMax/2);
	    setMedium(cx-i, cy+y, mediumMax/2);
	}
	    }
	    for (i = 0; i != windowWidth; i++)
	if (medium[windowOffsetX+i+gw*cy] == 0)
	    setWall(windowOffsetX+i, cy);
	    setFreqBar(19);
	}
	Setup createNext() { return new CircularPrismSetup(); }
    }
    class CircularPrismSetup extends Setup {
	String getName() { return "Circular Prism"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    int x0 = windowWidth/3-2;
	    int y0 = x0;
	    double r = (x0*x0+y0*y0)/(2.*y0);
	    double h = r-y0;
	    double r2 = r*r;
	    for (i = 0; i < x0; i++) {
	int y = (int) (Math.sqrt(r2-i*i)-h+.5);
	for (; y >= 0; y--) {
	    setMedium(cx+i, cy+y, mediumMax);
	    setMedium(cx-i, cy+y, mediumMax);
	    setMedium(cx+i, cy-y, mediumMax);
	    setMedium(cx-i, cy-y, mediumMax);
	}
	    }
	    for (i = 0; i != windowWidth; i++)
	if (medium[windowOffsetX+i+gw*cy] == 0)
	    setWall(windowOffsetX+i, cy);
	    setFreqBar(9);
	}
	Setup createNext() { return new RightAnglePrismSetup(); }
    }
    class RightAnglePrismSetup extends Setup {
	String getName() { return "Right-Angle Prism"; }
	void select() {
	    if (resBar.getValue() < 42)
	setResolution(42);
	    sourceChooser.select(SRC_1S1F_PLANE);
	    int i, j;
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    int x0 = windowWidth/4;
	    int y0 = x0;
	    for (i = -x0; i < x0; i++)
	for (j = -y0; j <= i; j++)
	    setMedium(cx+i, cy+j, mediumMax);
	    for (i = 0; i != windowWidth; i++)
	if (medium[windowOffsetX+i+gw*(cy-y0)] == 0)
	    setWall(windowOffsetX+i, cy-y0);
	    setFreqBar(11);
	}
	Setup createNext() { return new PorroPrismSetup(); }
    }
    class PorroPrismSetup extends Setup {
	String getName() { return "Porro Prism"; }
	void select() {
	    if (resBar.getValue() < 42)
	setResolution(42);
	    sourceChooser.select(SRC_1S1F_PLANE);
	    setSources();
	    int i, j;
	    int cx = gridSizeX/2;
	    sources[1].x = cx-1;
	    int x0 = windowWidth/2;
	    int y0 = x0;
	    int cy = gridSizeY/2-y0/2;
	    for (i = -x0; i < x0; i++) {
	int j2 = y0+1-((i < 0) ? -i : i);
	for (j = 0; j <= j2; j++)
	    setMedium(cx+i, cy+j, mediumMax);
	    }
	    for (i = 0; i != cy; i++)
	if (medium[cx+gw*(i+windowOffsetY)] == 0)
	    setWall(cx, i+windowOffsetY);
	    setFreqBar(11);
	}
	void doSetupSources() {}
	Setup createNext() { return new ScatteringSetup(); }
    }
    class ScatteringSetup extends Setup {
	String getName() { return "Scattering"; }
	void select() {
	    sourceChooser.select(SRC_1S1F_PLANE_PULSE);
	    int cx = gridSizeX/2;
	    int cy = gridSizeY/2;
	    setWall(cx, cy);
	    setFreqBar(1);
	    dampingBar.setValue(40);
	    setBrightness(52);
	}
	Setup createNext() { return new LloydsMirrorSetup(); }
    }
    class LloydsMirrorSetup extends Setup {
	String getName() { return "Lloyd's Mirror"; }
	void select() {
	    setSources();
	    sources[0].x = windowOffsetX;
	    sources[0].y = windowOffsetY + windowHeight*3/4;
	    setBrightness(75);
	    setFreqBar(23);
	    int i;
	    for (i = 0; i != windowWidth; i++)
	setWall(i+windowOffsetX, windowOffsetY+windowHeight-1);
	}
	void doSetupSources() {}
	Setup createNext() { return new TempGradient1(); }
    }
    class TempGradient1 extends Setup {
	String getName() { return "Temperature Gradient 1"; }
	void select() {
	    int i, j;
	    int j1 = windowOffsetY + windowHeight/2;
	    int j2 = windowOffsetY + windowHeight*3/4;
	    int j3 = windowOffsetY + windowHeight*7/8;
	    for (j = 0; j != gridSizeY; j++) {
	int m;
	if (j < j1)
	    m = 0;
	else if (j > j2)
	    m = mediumMax;
	else
	    m = mediumMax*(j-j1)/(j2-j1);
	for (i = 0; i != gridSizeX; i++)
	    setMedium(i, j, m);
	    }
	    for (i = j3; i < windowOffsetY+windowHeight; i++)
	setWall(gridSizeX/2, i);
	    setBrightness(33);
	}
	void doSetupSources() {
	    setSources();
	    sources[0].x = windowOffsetX+2;
	    sources[0].y = windowOffsetY+windowHeight-2;
	}
	Setup createNext() { return new TempGradient2(); }
    }
    class TempGradient2 extends Setup {
	String getName() { return "Temperature Gradient 2"; }
	void select() {
	    int i, j;
	    int j1 = windowOffsetY + windowHeight/2 - windowHeight/8;
	    int j2 = windowOffsetY + windowHeight/2 + windowHeight/8;
	    for (j = 0; j != gridSizeY; j++) {
	int m;
	if (j < j1)
	    m = mediumMax;
	else if (j > j2)
	    m = 0;
	else
	    m = mediumMax*(j2-j)/(j2-j1);
	for (i = 0; i != gridSizeX; i++)
	    setMedium(i, j, m);
	    }
	    setBrightness(31);
	}
	void doSetupSources() {
	    setSources();
	    sources[0].x = windowOffsetX+2;
	    sources[0].y = windowOffsetY+windowHeight/4;
	}
	Setup createNext() { return new TempGradient3(); }
    }
    class TempGradient3 extends Setup {
	String getName() { return "Temperature Gradient 3"; }
	void select() {
	    int i, j;
	    int j1 = windowOffsetY + windowHeight/2 - windowHeight/5;
	    int j2 = windowOffsetY + windowHeight/2 + windowHeight/5;
	    int j3 = gridSizeY/2;
	    for (j = 0; j != gridSizeY; j++) {
	int m;
	if (j < j1 || j > j2)
	    m = mediumMax;
	else if (j > j3)
	    m = mediumMax*(j-j3)/(j2-j3);
	else
	    m = mediumMax*(j3-j)/(j3-j1);
	for (i = 0; i != gridSizeX; i++)
	    setMedium(i, j, m);
	    }
	    setBrightness(31);
	}
	void doSetupSources() {
	    setSources();
	    sources[0].x = windowOffsetX+2;
	    sources[0].y = windowOffsetY+windowHeight/4;
	}
	Setup createNext() { return new TempGradient4(); }
    }
    class TempGradient4 extends TempGradient3 {
	String getName() { return "Temperature Gradient 4"; }
	void select() {
	    int i, j;
	    int j1 = windowOffsetY + windowHeight/2 - windowHeight/5;
	    int j2 = windowOffsetY + windowHeight/2 + windowHeight/5;
	    int j3 = gridSizeY/2;
	    for (j = 0; j != gridSizeY; j++) {
	int m;
	if (j < j1 || j > j2)
	    m = 0;
	else if (j > j3)
	    m = mediumMax*(j2-j)/(j2-j3);
	else
	    m = mediumMax*(j-j1)/(j3-j1);
	for (i = 0; i != gridSizeX; i++)
	    setMedium(i, j, m);
	    }
	    setBrightness(31);
	}
	Setup createNext() { return new DispersionSetup(); }
    }
    class DispersionSetup extends Setup {
	String getName() { return "Dispersion"; }
	void select() {
	    sourceChooser.select(SRC_2S2F);
	    int i, j;
	    for (i = 0; i != gridSizeY; i++)
	setWall(gridSizeX/2, i);
	    for (i = 0; i != gridSizeX; i++)
	for (j = 0; j != gridSizeY; j++)
	    setMedium(i, j, mediumMax/3);
	    fixedEndsCheck.setState(false);
	    setBrightness(16);
	}
	void doSetupSources() {
	    setSources();
	    sources[0].x = gridSizeX/2-2;
	    sources[1].x = gridSizeX/2+2;
	    sources[0].y = sources[1].y = windowOffsetY+1;
	    setFreqBar(7);
	    auxBar.setValue(30);
	}
	Setup createNext() { return null; }
    }
}
```


*s21.postimg.org/plyybdcer/Ripple.png


3) Write a simple PacMan game in Java?

Pacman is an arcade game originally developed by a Japanese company Namco in 1980. Pacman became one of the most popular arcade games ever created.


The following code example is a remake of a Pacman game by Brian Postma available at *www.brianpostma.com. The code is modified and simplified so that it is easier to understand.

The goal of the game is to collect all the points in the maze and avoid the ghosts. The Pacman is animated in two ways: his position in the maze and his body. We animate his body with four images, depending on the direction. The animation is used to create the illusion of Pacman opening and closing his mouth. The maze consists of 15x15 squares. The structure of the maze is based on a simple array of integers. Pacman has three lives. We also count the score.

The game consists of two files: Board.java and Pacman.java.


Board.java


```
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Board extends JPanel implements ActionListener {

    private Dimension d;
    private final Font smallfont = new Font("Helvetica", Font.BOLD, 14);

    private Image ii;
    private final Color dotcolor = new Color(192, 192, 0);
    private Color mazecolor;

    private boolean ingame = false;
    private boolean dying = false;

    private final int blocksize = 24;
    private final int nrofblocks = 15;
    private final int scrsize = nrofblocks * blocksize;
    private final int pacanimdelay = 2;
    private final int pacmananimcount = 4;
    private final int maxghosts = 12;
    private final int pacmanspeed = 6;

    private int pacanimcount = pacanimdelay;
    private int pacanimdir = 1;
    private int pacmananimpos = 0;
    private int nrofghosts = 6;
    private int pacsleft, score;
    private int[] dx, dy;
    private int[] ghostx, ghosty, ghostdx, ghostdy, ghostspeed;

    private Image ghost;
    private Image pacman1, pacman2up, pacman2left, pacman2right, pacman2down;
    private Image pacman3up, pacman3down, pacman3left, pacman3right;
    private Image pacman4up, pacman4down, pacman4left, pacman4right;

    private int pacmanx, pacmany, pacmandx, pacmandy;
    private int reqdx, reqdy, viewdx, viewdy;

    private final short leveldata[] = {
        19, 26, 26, 26, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 22,
        21, 0, 0, 0, 17, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20,
        21, 0, 0, 0, 17, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20,
        21, 0, 0, 0, 17, 16, 16, 24, 16, 16, 16, 16, 16, 16, 20,
        17, 18, 18, 18, 16, 16, 20, 0, 17, 16, 16, 16, 16, 16, 20,
        17, 16, 16, 16, 16, 16, 20, 0, 17, 16, 16, 16, 16, 24, 20,
        25, 16, 16, 16, 24, 24, 28, 0, 25, 24, 24, 16, 20, 0, 21,
        1, 17, 16, 20, 0, 0, 0, 0, 0, 0, 0, 17, 20, 0, 21,
        1, 17, 16, 16, 18, 18, 22, 0, 19, 18, 18, 16, 20, 0, 21,
        1, 17, 16, 16, 16, 16, 20, 0, 17, 16, 16, 16, 20, 0, 21,
        1, 17, 16, 16, 16, 16, 20, 0, 17, 16, 16, 16, 20, 0, 21,
        1, 17, 16, 16, 16, 16, 16, 18, 16, 16, 16, 16, 20, 0, 21,
        1, 17, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 0, 21,
        1, 25, 24, 24, 24, 24, 24, 24, 24, 24, 16, 16, 16, 18, 20,
        9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 25, 24, 24, 24, 28
    };

    private final int validspeeds[] = {1, 2, 3, 4, 6, 8};
    private final int maxspeed = 6;

    private int currentspeed = 3;
    private short[] screendata;
    private Timer timer;

    public Board() {

        loadImages();
        initVariables();
        
        addKeyListener(new TAdapter());

        setFocusable(true);

        setBackground(Color.black);
        setDoubleBuffered(true);
    }

    private void initVariables() {

        screendata = new short[nrofblocks * nrofblocks];
        mazecolor = new Color(5, 100, 5);
        d = new Dimension(400, 400);
        ghostx = new int[maxghosts];
        ghostdx = new int[maxghosts];
        ghosty = new int[maxghosts];
        ghostdy = new int[maxghosts];
        ghostspeed = new int[maxghosts];
        dx = new int[4];
        dy = new int[4];
        
        timer = new Timer(40, this);
        timer.start();
    }

    @Override
    public void addNotify() {
        super.addNotify();

        initGame();
    }

    private void doAnim() {

        pacanimcount--;

        if (pacanimcount <= 0) {
            pacanimcount = pacanimdelay;
            pacmananimpos = pacmananimpos + pacanimdir;

            if (pacmananimpos == (pacmananimcount - 1) || pacmananimpos == 0) {
                pacanimdir = -pacanimdir;
            }
        }
    }

    private void playGame(Graphics2D g2d) {

        if (dying) {

            death();

        } else {

            movePacman();
            drawPacman(g2d);
            moveGhosts(g2d);
            checkMaze();
        }
    }

    private void showIntroScreen(Graphics2D g2d) {

        g2d.setColor(new Color(0, 32, 48));
        g2d.fillRect(50, scrsize / 2 - 30, scrsize - 100, 50);
        g2d.setColor(Color.white);
        g2d.drawRect(50, scrsize / 2 - 30, scrsize - 100, 50);

        String s = "Press s to start.";
        Font small = new Font("Helvetica", Font.BOLD, 14);
        FontMetrics metr = this.getFontMetrics(small);

        g2d.setColor(Color.white);
        g2d.setFont(small);
        g2d.drawString(s, (scrsize - metr.stringWidth(s)) / 2, scrsize / 2);
    }

    private void drawScore(Graphics2D g) {

        int i;
        String s;

        g.setFont(smallfont);
        g.setColor(new Color(96, 128, 255));
        s = "Score: " + score;
        g.drawString(s, scrsize / 2 + 96, scrsize + 16);

        for (i = 0; i < pacsleft; i++) {
            g.drawImage(pacman3left, i * 28 + 8, scrsize + 1, this);
        }
    }

    private void checkMaze() {

        short i = 0;
        boolean finished = true;

        while (i < nrofblocks * nrofblocks && finished) {

            if ((screendata[i] & 48) != 0) {
                finished = false;
            }

            i++;
        }

        if (finished) {

            score += 50;

            if (nrofghosts < maxghosts) {
                nrofghosts++;
            }

            if (currentspeed < maxspeed) {
                currentspeed++;
            }

            initLevel();
        }
    }

    private void death() {

        pacsleft--;

        if (pacsleft == 0) {
            ingame = false;
        }

        continueLevel();
    }

    private void moveGhosts(Graphics2D g2d) {

        short i;
        int pos;
        int count;

        for (i = 0; i < nrofghosts; i++) {
            if (ghostx[i] % blocksize == 0 && ghosty[i] % blocksize == 0) {
                pos = ghostx[i] / blocksize + nrofblocks * (int) (ghosty[i] / blocksize);

                count = 0;

                if ((screendata[pos] & 1) == 0 && ghostdx[i] != 1) {
                    dx[count] = -1;
                    dy[count] = 0;
                    count++;
                }

                if ((screendata[pos] & 2) == 0 && ghostdy[i] != 1) {
                    dx[count] = 0;
                    dy[count] = -1;
                    count++;
                }

                if ((screendata[pos] & 4) == 0 && ghostdx[i] != -1) {
                    dx[count] = 1;
                    dy[count] = 0;
                    count++;
                }

                if ((screendata[pos] & 8) == 0 && ghostdy[i] != -1) {
                    dx[count] = 0;
                    dy[count] = 1;
                    count++;
                }

                if (count == 0) {

                    if ((screendata[pos] & 15) == 15) {
                        ghostdx[i] = 0;
                        ghostdy[i] = 0;
                    } else {
                        ghostdx[i] = -ghostdx[i];
                        ghostdy[i] = -ghostdy[i];
                    }

                } else {

                    count = (int) (Math.random() * count);

                    if (count > 3) {
                        count = 3;
                    }

                    ghostdx[i] = dx[count];
                    ghostdy[i] = dy[count];
                }

            }

            ghostx[i] = ghostx[i] + (ghostdx[i] * ghostspeed[i]);
            ghosty[i] = ghosty[i] + (ghostdy[i] * ghostspeed[i]);
            drawGhost(g2d, ghostx[i] + 1, ghosty[i] + 1);

            if (pacmanx > (ghostx[i] - 12) && pacmanx < (ghostx[i] + 12)
                    && pacmany > (ghosty[i] - 12) && pacmany < (ghosty[i] + 12)
                    && ingame) {

                dying = true;
            }
        }
    }

    private void drawGhost(Graphics2D g2d, int x, int y) {

        g2d.drawImage(ghost, x, y, this);
    }

    private void movePacman() {

        int pos;
        short ch;

        if (reqdx == -pacmandx && reqdy == -pacmandy) {
            pacmandx = reqdx;
            pacmandy = reqdy;
            viewdx = pacmandx;
            viewdy = pacmandy;
        }

        if (pacmanx % blocksize == 0 && pacmany % blocksize == 0) {
            pos = pacmanx / blocksize + nrofblocks * (int) (pacmany / blocksize);
            ch = screendata[pos];

            if ((ch & 16) != 0) {
                screendata[pos] = (short) (ch & 15);
                score++;
            }

            if (reqdx != 0 || reqdy != 0) {
                if (!((reqdx == -1 && reqdy == 0 && (ch & 1) != 0)
                        || (reqdx == 1 && reqdy == 0 && (ch & 4) != 0)
                        || (reqdx == 0 && reqdy == -1 && (ch & 2) != 0)
                        || (reqdx == 0 && reqdy == 1 && (ch & 8) != 0))) {
                    pacmandx = reqdx;
                    pacmandy = reqdy;
                    viewdx = pacmandx;
                    viewdy = pacmandy;
                }
            }

            // Check for standstill
            if ((pacmandx == -1 && pacmandy == 0 && (ch & 1) != 0)
                    || (pacmandx == 1 && pacmandy == 0 && (ch & 4) != 0)
                    || (pacmandx == 0 && pacmandy == -1 && (ch & 2) != 0)
                    || (pacmandx == 0 && pacmandy == 1 && (ch & 8) != 0)) {
                pacmandx = 0;
                pacmandy = 0;
            }
        }
        pacmanx = pacmanx + pacmanspeed * pacmandx;
        pacmany = pacmany + pacmanspeed * pacmandy;
    }

    private void drawPacman(Graphics2D g2d) {

        if (viewdx == -1) {
            drawPacnanLeft(g2d);
        } else if (viewdx == 1) {
            drawPacmanRight(g2d);
        } else if (viewdy == -1) {
            drawPacmanUp(g2d);
        } else {
            drawPacmanDown(g2d);
        }
    }

    private void drawPacmanUp(Graphics2D g2d) {

        switch (pacmananimpos) {
            case 1:
                g2d.drawImage(pacman2up, pacmanx + 1, pacmany + 1, this);
                break;
            case 2:
                g2d.drawImage(pacman3up, pacmanx + 1, pacmany + 1, this);
                break;
            case 3:
                g2d.drawImage(pacman4up, pacmanx + 1, pacmany + 1, this);
                break;
            default:
                g2d.drawImage(pacman1, pacmanx + 1, pacmany + 1, this);
                break;
        }
    }

    private void drawPacmanDown(Graphics2D g2d) {

        switch (pacmananimpos) {
            case 1:
                g2d.drawImage(pacman2down, pacmanx + 1, pacmany + 1, this);
                break;
            case 2:
                g2d.drawImage(pacman3down, pacmanx + 1, pacmany + 1, this);
                break;
            case 3:
                g2d.drawImage(pacman4down, pacmanx + 1, pacmany + 1, this);
                break;
            default:
                g2d.drawImage(pacman1, pacmanx + 1, pacmany + 1, this);
                break;
        }
    }

    private void drawPacnanLeft(Graphics2D g2d) {

        switch (pacmananimpos) {
            case 1:
                g2d.drawImage(pacman2left, pacmanx + 1, pacmany + 1, this);
                break;
            case 2:
                g2d.drawImage(pacman3left, pacmanx + 1, pacmany + 1, this);
                break;
            case 3:
                g2d.drawImage(pacman4left, pacmanx + 1, pacmany + 1, this);
                break;
            default:
                g2d.drawImage(pacman1, pacmanx + 1, pacmany + 1, this);
                break;
        }
    }

    private void drawPacmanRight(Graphics2D g2d) {

        switch (pacmananimpos) {
            case 1:
                g2d.drawImage(pacman2right, pacmanx + 1, pacmany + 1, this);
                break;
            case 2:
                g2d.drawImage(pacman3right, pacmanx + 1, pacmany + 1, this);
                break;
            case 3:
                g2d.drawImage(pacman4right, pacmanx + 1, pacmany + 1, this);
                break;
            default:
                g2d.drawImage(pacman1, pacmanx + 1, pacmany + 1, this);
                break;
        }
    }

    private void drawMaze(Graphics2D g2d) {

        short i = 0;
        int x, y;

        for (y = 0; y < scrsize; y += blocksize) {
            for (x = 0; x < scrsize; x += blocksize) {

                g2d.setColor(mazecolor);
                g2d.setStroke(new BasicStroke(2));

                if ((screendata[i] & 1) != 0) { 
                    g2d.drawLine(x, y, x, y + blocksize - 1);
                }

                if ((screendata[i] & 2) != 0) { 
                    g2d.drawLine(x, y, x + blocksize - 1, y);
                }

                if ((screendata[i] & 4) != 0) { 
                    g2d.drawLine(x + blocksize - 1, y, x + blocksize - 1,
                            y + blocksize - 1);
                }

                if ((screendata[i] & 8) != 0) { 
                    g2d.drawLine(x, y + blocksize - 1, x + blocksize - 1,
                            y + blocksize - 1);
                }

                if ((screendata[i] & 16) != 0) { 
                    g2d.setColor(dotcolor);
                    g2d.fillRect(x + 11, y + 11, 2, 2);
                }

                i++;
            }
        }
    }

    private void initGame() {

        pacsleft = 3;
        score = 0;
        initLevel();
        nrofghosts = 6;
        currentspeed = 3;
    }

    private void initLevel() {

        int i;
        for (i = 0; i < nrofblocks * nrofblocks; i++) {
            screendata[i] = leveldata[i];
        }

        continueLevel();
    }

    private void continueLevel() {

        short i;
        int dx = 1;
        int random;

        for (i = 0; i < nrofghosts; i++) {

            ghosty[i] = 4 * blocksize;
            ghostx[i] = 4 * blocksize;
            ghostdy[i] = 0;
            ghostdx[i] = dx;
            dx = -dx;
            random = (int) (Math.random() * (currentspeed + 1));

            if (random > currentspeed) {
                random = currentspeed;
            }

            ghostspeed[i] = validspeeds[random];
        }

        pacmanx = 7 * blocksize;
        pacmany = 11 * blocksize;
        pacmandx = 0;
        pacmandy = 0;
        reqdx = 0;
        reqdy = 0;
        viewdx = -1;
        viewdy = 0;
        dying = false;
    }

    private void loadImages() {

        ghost = new ImageIcon("images/ghost.png").getImage();
        pacman1 = new ImageIcon("images/pacman.png").getImage();
        pacman2up = new ImageIcon("images/up1.png").getImage();
        pacman3up = new ImageIcon("images/up2.png").getImage();
        pacman4up = new ImageIcon("images/up3.png").getImage();
        pacman2down = new ImageIcon("images/down1.png").getImage();
        pacman3down = new ImageIcon("images/down2.png").getImage();
        pacman4down = new ImageIcon("images/down3.png").getImage();
        pacman2left = new ImageIcon("images/left1.png").getImage();
        pacman3left = new ImageIcon("images/left2.png").getImage();
        pacman4left = new ImageIcon("images/left3.png").getImage();
        pacman2right = new ImageIcon("images/right1.png").getImage();
        pacman3right = new ImageIcon("images/right2.png").getImage();
        pacman4right = new ImageIcon("images/right3.png").getImage();

    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        doDrawing(g);
    }

    private void doDrawing(Graphics g) {

        Graphics2D g2d = (Graphics2D) g;

        g2d.setColor(Color.black);
        g2d.fillRect(0, 0, d.width, d.height);

        drawMaze(g2d);
        drawScore(g2d);
        doAnim();

        if (ingame) {
            playGame(g2d);
        } else {
            showIntroScreen(g2d);
        }

        g2d.drawImage(ii, 5, 5, this);
        Toolkit.getDefaultToolkit().sync();
        g2d.dispose();
    }

    class TAdapter extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent e) {

            int key = e.getKeyCode();

            if (ingame) {
                if (key == KeyEvent.VK_LEFT) {
                    reqdx = -1;
                    reqdy = 0;
                } else if (key == KeyEvent.VK_RIGHT) {
                    reqdx = 1;
                    reqdy = 0;
                } else if (key == KeyEvent.VK_UP) {
                    reqdx = 0;
                    reqdy = -1;
                } else if (key == KeyEvent.VK_DOWN) {
                    reqdx = 0;
                    reqdy = 1;
                } else if (key == KeyEvent.VK_ESCAPE && timer.isRunning()) {
                    ingame = false;
                } else if (key == KeyEvent.VK_PAUSE) {
                    if (timer.isRunning()) {
                        timer.stop();
                    } else {
                        timer.start();
                    }
                }
            } else {
                if (key == 's' || key == 'S') {
                    ingame = true;
                    initGame();
                }
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {

            int key = e.getKeyCode();

            if (key == Event.LEFT || key == Event.RIGHT
                    || key == Event.UP || key == Event.DOWN) {
                reqdx = 0;
                reqdy = 0;
            }
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        repaint();
    }
}
```
The Pacman is controlled with the cursor keys. The Esc key finishes the game, the Pause key pauses it.

private int pacmanx, pacmany, pacmandx, pacmandy;
The first two variables store the x and y coordinates of the Pacman sprite. The last two variables are the delta changes in horizontal and vertical directions.

private final short leveldata[] = {
    19, 26, 26, 26, 18, 18, 18, 18, ...
};
These numbers make up the maze. They provide information out of which we create the corners and the points. Number 1 is a left corner. Numbers 2, 4 and 8 represent top, right, bottom corners respectively. Number 16 is a point. These number can be added, for example number 19 in the upper left corner means that the square will have top and left borders and a point (16 + 2 + 1).


```
private void doAnim() {

    pacanimcount--;

    if (pacanimcount <= 0) {
        pacanimcount = pacanimdelay;
        pacmananimpos = pacmananimpos + pacanimdir;

        if (pacmananimpos == (pacmananimcount - 1) || pacmananimpos == 0) {
            pacanimdir = -pacanimdir;
        }
    }
}
```
The doAnim() counts the pacmananimpos variable which determines what pacman image is drawn. There are four pacman images. There is also a pacanimdelay variable which makes the animation a bit slower. Otherwise the pacman would open his mouth too fast.


```
boolean finished = true;

while (i < nrofblocks * nrofblocks && finished) {

    if ((screendata[i] & 48) != 0) {
        finished = false;
    }

    i++;
}
```
This code is part of the checkMaze() method. It checks if there are any points left for the Pacman to eat. Number 16 stands for a point. If all points are consumed, we move to the next level. (In our case, we just restart the game.)

Next we will examine the moveGhosts() method. The ghosts move one square and then decide if they change the direction.


```
if (ghostx[i] % blocksize == 0 && ghosty[i] % blocksize == 0) {
We continue only if we have finished moving one square.

pos = ghostx[i] / blocksize + nrofblocks * (int)(ghosty[i] / blocksize);
This line determines where the ghost is situated. In which position/square. There are 225 theoretical positions. (A ghost cannot move over walls.)

if ((screendata[pos] & 1) == 0 && ghostdx[i] != 1) {
    dx[count] = -1;
    dy[count] = 0;
    count++;
}
If there is no obstacle on the left and the ghost is not already moving to the right, the ghost will move to the left. What does this code really mean? If the ghost enters a tunnel, he will continue in the same direction until he is out of the tunnel. Moving of ghosts is partly random. We do not apply this randomness inside long tunnels. The ghost might get stuck there.

if (pacmanx > (ghostx[i] - 12) && pacmanx < (ghostx[i] + 12)
        && pacmany > (ghosty[i] - 12) && pacmany < (ghosty[i] + 12)
        && ingame) {

    dying = true;
}
If there is a collision between ghosts and a Pacman, the Pacman dies.

Next we are going to examine the movePacman() method. The reqdx and reqdy variables are determined in the TAdapter inner class. These variables are controlled with cursor keys.

if ((ch & 16) != 0) {
    screendata[pos] = (short) (ch & 15);
    score++;
}
If the pacman moves to a position with a point, we remove it from the maze and increase the score value.

if ((pacmandx == -1 && pacmandy == 0 && (ch & 1) != 0) ||
    (pacmandx == 1 && pacmandy == 0 && (ch & 4) != 0) ||
    (pacmandx == 0 && pacmandy == -1 && (ch & 2) != 0) ||
    (pacmandx == 0 && pacmandy == 1 && (ch & 8) != 0)) {
    pacmandx = 0;
    pacmandy = 0;
}
The Pacman stops if he cannot move further it his current direction.

private void drawPacman(Graphics2D g2d) {

    if (viewdx == -1) {
        drawPacnanLeft(g2d);
    } else if (viewdx == 1) {
        drawPacmanRight(g2d);
    } else if (viewdy == -1) {
        drawPacmanUp(g2d);
    } else {
        drawPacmanDown(g2d);
    }
}
There are four possible directions for a Pacman. There are four images for all directions. The images are used to animate Pacman opening and closing his mouth.

The drawMaze() method draws the maze out of the numbers in the screendata array. Number 1 is a left border, 2 is a top border, 4 is a right border, 8 is a bottom border and 16 is a point. We simply go through all 225 squares in the maze. For example we have 9 in the screendata array. We have the first bit (1) and the fourth bit (8) set. So we draw a bottom and a left border on this particular square.

if ((screendata[i] & 1) != 0) { 
    g2d.drawLine(x, y, x, y + blocksize - 1);
}
```

We draw a left border if the first bit of a number is set.

Pacman.java


```
import java.awt.EventQueue;
import javax.swing.JFrame;

public class Pacman extends JFrame {

    public Pacman() {
        
        initUI();
    }
    
    private void initUI() {
        
        add(new Board());
        setTitle("Pacman");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(380, 420);
        setLocationRelativeTo(null);
        setVisible(true);        
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                Pacman ex = new Pacman();
                ex.setVisible(true);
            }
        });
    }
}
```

*s22.postimg.org/sc90uux9p/pacman.png

This is a Pacman file with a main method.

4) Write a simple Snake game?

Snake is an older classic video game. It was first created in late 70s. Later it was brought to PCs. In this game the player controls a snake. The objective is to eat as many apples as possible. Each time the snake eats an apple, its body grows. The snake must avoid the walls and its own body. This game is sometimes called Nibbles.



The size of each of the joints of a snake is 10px. The snake is controlled with the cursor keys. Initially, the snake has three joints. If the game is finished, the "Game Over" message is displayed in the middle of the board.

Board.java


```
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Board extends JPanel implements ActionListener {

    private final int B_WIDTH = 300;
    private final int B_HEIGHT = 300;
    private final int DOT_SIZE = 10;
    private final int ALL_DOTS = 900;
    private final int RAND_POS = 29;
    private final int DELAY = 140;

    private final int x[] = new int[ALL_DOTS];
    private final int y[] = new int[ALL_DOTS];

    private int dots;
    private int apple_x;
    private int apple_y;

    private boolean leftDirection = false;
    private boolean rightDirection = true;
    private boolean upDirection = false;
    private boolean downDirection = false;
    private boolean inGame = true;

    private Timer timer;
    private Image ball;
    private Image apple;
    private Image head;

    public Board() {

        addKeyListener(new TAdapter());
        setBackground(Color.black);
        setFocusable(true);

        setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
        loadImages();
        initGame();
    }

    private void loadImages() {

        ImageIcon iid = new ImageIcon("dot.png");
        ball = iid.getImage();

        ImageIcon iia = new ImageIcon("apple.png");
        apple = iia.getImage();

        ImageIcon iih = new ImageIcon("head.png");
        head = iih.getImage();
    }

    private void initGame() {

        dots = 3;

        for (int z = 0; z < dots; z++) {
            x[z] = 50 - z * 10;
            y[z] = 50;
        }

        locateApple();

        timer = new Timer(DELAY, this);
        timer.start();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        doDrawing(g);
    }
    
    private void doDrawing(Graphics g) {
        
        if (inGame) {

            g.drawImage(apple, apple_x, apple_y, this);

            for (int z = 0; z < dots; z++) {
                if (z == 0) {
                    g.drawImage(head, x[z], y[z], this);
                } else {
                    g.drawImage(ball, x[z], y[z], this);
                }
            }

            Toolkit.getDefaultToolkit().sync();

        } else {

            gameOver(g);
        }        
    }

    private void gameOver(Graphics g) {
        
        String msg = "Game Over";
        Font small = new Font("Helvetica", Font.BOLD, 14);
        FontMetrics metr = getFontMetrics(small);

        g.setColor(Color.white);
        g.setFont(small);
        g.drawString(msg, (B_WIDTH - metr.stringWidth(msg)) / 2, B_HEIGHT / 2);
    }

    private void checkApple() {

        if ((x[0] == apple_x) && (y[0] == apple_y)) {

            dots++;
            locateApple();
        }
    }

    private void move() {

        for (int z = dots; z > 0; z--) {
            x[z] = x[(z - 1)];
            y[z] = y[(z - 1)];
        }

        if (leftDirection) {
            x[0] -= DOT_SIZE;
        }

        if (rightDirection) {
            x[0] += DOT_SIZE;
        }

        if (upDirection) {
            y[0] -= DOT_SIZE;
        }

        if (downDirection) {
            y[0] += DOT_SIZE;
        }
    }

    private void checkCollision() {

        for (int z = dots; z > 0; z--) {

            if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
                inGame = false;
            }
        }

        if (y[0] >= B_HEIGHT) {
            inGame = false;
        }

        if (y[0] < 0) {
            inGame = false;
        }

        if (x[0] >= B_WIDTH) {
            inGame = false;
        }

        if (x[0] < 0) {
            inGame = false;
        }
        
        if(!inGame) {
            timer.stop();
        }
    }

    private void locateApple() {

        int r = (int) (Math.random() * RAND_POS);
        apple_x = ((r * DOT_SIZE));

        r = (int) (Math.random() * RAND_POS);
        apple_y = ((r * DOT_SIZE));
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        if (inGame) {

            checkApple();
            checkCollision();
            move();
        }

        repaint();
    }

    private class TAdapter extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent e) {

            int key = e.getKeyCode();

            if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) {
                leftDirection = true;
                upDirection = false;
                downDirection = false;
            }

            if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) {
                rightDirection = true;
                upDirection = false;
                downDirection = false;
            }

            if ((key == KeyEvent.VK_UP) && (!downDirection)) {
                upDirection = true;
                rightDirection = false;
                leftDirection = false;
            }

            if ((key == KeyEvent.VK_DOWN) && (!upDirection)) {
                downDirection = true;
                rightDirection = false;
                leftDirection = false;
            }
        }
    }
}
```

First we will define the constants used in our game.


```
private final int B_WIDTH = 300;
private final int B_HEIGHT = 300;
private final int DOT_SIZE = 10;
private final int ALL_DOTS = 900;
private final int RAND_POS = 29;
private final int DELAY = 140;
The B_WIDTH and B_HEIGHT constants determine the size of the board. The DOT_SIZE is the size of the apple and the dot of the snake. The ALL_DOTS constant defines the maximum number of possible dots on the board (900 = (300*300)/(10*10)). The RAND_POS constant is used to calculate a random position for an apple. The DELAY constant determines the speed of the game.

private final int x[] = new int[ALL_DOTS];
private final int y[] = new int[ALL_DOTS];
These two arrays store the x and y coordinates of all joints of a snake.

private void loadImages() {

    ImageIcon iid = new ImageIcon("dot.png");
    ball = iid.getImage();

    ImageIcon iia = new ImageIcon("apple.png");
    apple = iia.getImage();

    ImageIcon iih = new ImageIcon("head.png");
    head = iih.getImage();
}
In the loadImages() method we get the images for the game. The ImageIcon class is used for displaying PNG images.

private void initGame() {

    dots = 3;

    for (int z = 0; z < dots; z++) {
        x[z] = 50 - z * 10;
        y[z] = 50;
    }

    locateApple();

    timer = new Timer(DELAY, this);
    timer.start();
}
In the initGame() method we create the snake, randomly locate an apple on the board, and start the timer.

private void checkApple() {

    if ((x[0] == apple_x) && (y[0] == apple_y)) {

        dots++;
        locateApple();
    }
}
If the apple collides with the head, we increase the number of joints of the snake. We call the locateApple() method which randomly positions a new apple object.

In the move() method we have the key algorithm of the game. To understand it, look at how the snake is moving. We control the head of the snake. We can change its direction with the cursor keys. The rest of the joints move one position up the chain. The second joint moves where the first was, the third joint where the second was etc.

for (int z = dots; z > 0; z--) {
    x[z] = x[(z - 1)];
    y[z] = y[(z - 1)];
}
This code moves the joints up the chain.

if (leftDirection) {
    x[0] -= DOT_SIZE;
}
This line moves the head to the left.

In the checkCollision() method, we determine if the snake has hit itself or one of the walls.

for (int z = dots; z > 0; z--) {

    if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
        inGame = false;
    }
}
If the snake hits one of its joints with its head the game is over.

if (y[0] >= B_HEIGHT) {
    inGame = false;
}
```

The game is finished if the snake hits the bottom of the board.

Snake.java


```
import java.awt.EventQueue;
import javax.swing.JFrame;


public class Snake extends JFrame {

    public Snake() {

        add(new Board());
        
        setResizable(false);
        pack();
        
        setTitle("Snake");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {                
                JFrame ex = new Snake();
                ex.setVisible(true);                
            }
        });
    }
}
```

This is the main class.

*s13.postimg.org/vmvjvybcz/snake.png

setResizable(false);
pack();
The setResizable() method affects the insets of the JFrame container on some platforms. Therefore, it is important to call it before the pack() method. Otherwise, the collision of the snake's head with the right and bottom borders might not work correctly.

5) Write a simple Tetris game in Java?

The Tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a Russian programmer Alexey Pajitnov in 1985. Since then, Tetris is available on almost every computer platform in lots of variations. Even my mobile phone has a modified version of the Tetris game.


Tetris is called a falling block puzzle game. In this game, we have seven different shapes called tetrominoes. S-shape, Z-shape, T-shape, L-shape, Line-shape, MirroredL-shape and a Square-shape. Each of these shapes is formed with four squares. The shapes are falling down the board. The object of the Tetris game is to move and rotate the shapes, so that they fit as much as possible. If we manage to form a row, the row is destroyed and we score. We play the tetris game until we top out.

Tetrominoes
Figure: Tetrominoes
The development

We do not have images for our tetris game, we draw the tetrominoes using Swing drawing API. Behind every computer game, there is a mathematical model. So it is in Tetris.

Some ideas behind the game.

We use a Timer class to create a game cycle
The tetrominoes are drawn
The shapes move on a square by square basis (not pixel by pixel)
Mathematically a board is a simple list of numbers
I have simplified the game a bit, so that it is easier to understand. The game starts immediately, after it is launched. We can pause the game by pressing the p key. The space key will drop the tetris piece immediately to the bottom. The d key will drop the piece one line down. (It can be used to speed up the falling a bit.) The game goes at constant speed, no acceleration is implemented. The score is the number of lines that we have removed.

Tetris.java


```
import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;


public class Tetris extends JFrame {

    JLabel statusbar;


    public Tetris() {

        statusbar = new JLabel(" 0");
        add(statusbar, BorderLayout.SOUTH);
        Board board = new Board(this);
        add(board);
        board.start();

        setSize(200, 400);
        setTitle("Tetris");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
   }

   public JLabel getStatusBar() {
       return statusbar;
   }

    public static void main(String[] args) {

        Tetris game = new Tetris();
        game.setLocationRelativeTo(null);
        game.setVisible(true);

    } 
}
```
In the Tetris.java file, we set up the game. We create a board on which we play the game. We create a statusbar.

board.start();
The start() method starts the Tetris game. Immediately, after the window appears on the screen.

Shape.java


```
import java.util.Random;
import java.lang.Math;


public class Shape {

    enum Tetrominoes { NoShape, ZShape, SShape, LineShape, 
               TShape, SquareShape, LShape, MirroredLShape };

    private Tetrominoes pieceShape;
    private int coords[][];
    private int[][][] coordsTable;


    public Shape() {

        coords = new int[4][2];
        setShape(Tetrominoes.NoShape);

    }

    public void setShape(Tetrominoes shape) {

         coordsTable = new int[][][] {
            { { 0, 0 },   { 0, 0 },   { 0, 0 },   { 0, 0 } },
            { { 0, -1 },  { 0, 0 },   { -1, 0 },  { -1, 1 } },
            { { 0, -1 },  { 0, 0 },   { 1, 0 },   { 1, 1 } },
            { { 0, -1 },  { 0, 0 },   { 0, 1 },   { 0, 2 } },
            { { -1, 0 },  { 0, 0 },   { 1, 0 },   { 0, 1 } },
            { { 0, 0 },   { 1, 0 },   { 0, 1 },   { 1, 1 } },
            { { -1, -1 }, { 0, -1 },  { 0, 0 },   { 0, 1 } },
            { { 1, -1 },  { 0, -1 },  { 0, 0 },   { 0, 1 } }
        };

        for (int i = 0; i < 4 ; i++) {
            for (int j = 0; j < 2; ++j) {
                coords[i][j] = coordsTable[shape.ordinal()][i][j];
            }
        }
        pieceShape = shape;

    }

    private void setX(int index, int x) { coords[index][0] = x; }
    private void setY(int index, int y) { coords[index][1] = y; }
    public int x(int index) { return coords[index][0]; }
    public int y(int index) { return coords[index][1]; }
    public Tetrominoes getShape()  { return pieceShape; }

    public void setRandomShape()
    {
        Random r = new Random();
        int x = Math.abs(r.nextInt()) % 7 + 1;
        Tetrominoes[] values = Tetrominoes.values(); 
        setShape(values[x]);
    }

    public int minX()
    {
      int m = coords[0][0];
      for (int i=0; i < 4; i++) {
          m = Math.min(m, coords[i][0]);
      }
      return m;
    }


    public int minY() 
    {
      int m = coords[0][1];
      for (int i=0; i < 4; i++) {
          m = Math.min(m, coords[i][1]);
      }
      return m;
    }

    public Shape rotateLeft() 
    {
        if (pieceShape == Tetrominoes.SquareShape)
            return this;

        Shape result = new Shape();
        result.pieceShape = pieceShape;

        for (int i = 0; i < 4; ++i) {
            result.setX(i, y(i));
            result.setY(i, -x(i));
        }
        return result;
    }

    public Shape rotateRight()
    {
        if (pieceShape == Tetrominoes.SquareShape)
            return this;

        Shape result = new Shape();
        result.pieceShape = pieceShape;

        for (int i = 0; i < 4; ++i) {
            result.setX(i, -y(i));
            result.setY(i, x(i));
        }
        return result;
    }
}
```
The Shape class provides information about a tetris piece.

enum Tetrominoes { NoShape, ZShape, SShape, LineShape, 
        TShape, SquareShape, LShape, MirroredLShape };
The Tetrominoes enum holds all seven tetris shapes. Plus the empty shape called here NoShape.


```
public Shape() {

    coords = new int[4][2];
    setShape(Tetrominoes.NoShape);

}
This is the constructor of the Shape class. The coords array holds the actual coordinates of a Tetris piece.

coordsTable = new int[][][] {
   { { 0, 0 },   { 0, 0 },   { 0, 0 },   { 0, 0 } },
   { { 0, -1 },  { 0, 0 },   { -1, 0 },  { -1, 1 } },
   { { 0, -1 },  { 0, 0 },   { 1, 0 },   { 1, 1 } },
   { { 0, -1 },  { 0, 0 },   { 0, 1 },   { 0, 2 } },
   { { -1, 0 },  { 0, 0 },   { 1, 0 },   { 0, 1 } },
   { { 0, 0 },   { 1, 0 },   { 0, 1 },   { 1, 1 } },
   { { -1, -1 }, { 0, -1 },  { 0, 0 },   { 0, 1 } },
   { { 1, -1 },  { 0, -1 },  { 0, 0 },   { 0, 1 } }
};
The coordsTable array holds all possible coordinate values of our tetris pieces. This is a template from which all pieces take their coordiate values.

for (int i = 0; i < 4 ; i++) {
    for (int j = 0; j < 2; ++j) {
        coords[i][j] = coordsTable[shape.ordinal()][i][j];
    }
}
Here we put one row of the coordiate values from the coordsTable to a coords array of a tetris piece. Note the use of the ordinal() method. In C++, an enum type is esencially an integer. Unlike in C++, Java enums are full classes. And the ordinal() method returns the current position of the enum type in the enum object.

The following image will help understand the coordinate values a bit more. The coords array saves the coordinates of the tetris piece. For example, numbers { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 }, represent a rotated S-shape. The following diagram illustrates the shape.

Coordinates
Figure: Coordinates
 public Shape rotateLeft() 
{
    if (pieceShape == Tetrominoes.SquareShape)
        return this;

    Shape result = new Shape();
    result.pieceShape = pieceShape;

    for (int i = 0; i < 4; ++i) {
        result.setX(i, y(i));
        result.setY(i, -x(i));
    }
    return result;
 }
```
This code rotates the piece to the left. The square does not have to be rotated. That's why we simply return the reference to the current object. Looking at the previous image will help to understand the rotation.

Board.java


```
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

import tetris.Shape.Tetrominoes;


public class Board extends JPanel implements ActionListener {


    final int BoardWidth = 10;
    final int BoardHeight = 22;

    Timer timer;
    boolean isFallingFinished = false;
    boolean isStarted = false;
    boolean isPaused = false;
    int numLinesRemoved = 0;
    int curX = 0;
    int curY = 0;
    JLabel statusbar;
    Shape curPiece;
    Tetrominoes[] board;



    public Board(Tetris parent) {

       setFocusable(true);
       curPiece = new Shape();
       timer = new Timer(400, this);
       timer.start(); 

       statusbar =  parent.getStatusBar();
       board = new Tetrominoes[BoardWidth * BoardHeight];
       addKeyListener(new TAdapter());
       clearBoard();  
    }

    public void actionPerformed(ActionEvent e) {
        if (isFallingFinished) {
            isFallingFinished = false;
            newPiece();
        } else {
            oneLineDown();
        }
    }


    int squareWidth() { return (int) getSize().getWidth() / BoardWidth; }
    int squareHeight() { return (int) getSize().getHeight() / BoardHeight; }
    Tetrominoes shapeAt(int x, int y) { return board[(y * BoardWidth) + x]; }


    public void start()
    {
        if (isPaused)
            return;

        isStarted = true;
        isFallingFinished = false;
        numLinesRemoved = 0;
        clearBoard();

        newPiece();
        timer.start();
    }

    private void pause()
    {
        if (!isStarted)
            return;

        isPaused = !isPaused;
        if (isPaused) {
            timer.stop();
            statusbar.setText("paused");
        } else {
            timer.start();
            statusbar.setText(String.valueOf(numLinesRemoved));
        }
        repaint();
    }

    public void paint(Graphics g)
    { 
        super.paint(g);

        Dimension size = getSize();
        int boardTop = (int) size.getHeight() - BoardHeight * squareHeight();


        for (int i = 0; i < BoardHeight; ++i) {
            for (int j = 0; j < BoardWidth; ++j) {
                Tetrominoes shape = shapeAt(j, BoardHeight - i - 1);
                if (shape != Tetrominoes.NoShape)
                    drawSquare(g, 0 + j * squareWidth(),
                               boardTop + i * squareHeight(), shape);
            }
        }

        if (curPiece.getShape() != Tetrominoes.NoShape) {
            for (int i = 0; i < 4; ++i) {
                int x = curX + curPiece.x(i);
                int y = curY - curPiece.y(i);
                drawSquare(g, 0 + x * squareWidth(),
                           boardTop + (BoardHeight - y - 1) * squareHeight(),
                           curPiece.getShape());
            }
        }
    }

    private void dropDown()
    {
        int newY = curY;
        while (newY > 0) {
            if (!tryMove(curPiece, curX, newY - 1))
                break;
            --newY;
        }
        pieceDropped();
    }

    private void oneLineDown()
    {
        if (!tryMove(curPiece, curX, curY - 1))
            pieceDropped();
    }


    private void clearBoard()
    {
        for (int i = 0; i < BoardHeight * BoardWidth; ++i)
            board[i] = Tetrominoes.NoShape;
    }

    private void pieceDropped()
    {
        for (int i = 0; i < 4; ++i) {
            int x = curX + curPiece.x(i);
            int y = curY - curPiece.y(i);
            board[(y * BoardWidth) + x] = curPiece.getShape();
        }

        removeFullLines();

        if (!isFallingFinished)
            newPiece();
    }

    private void newPiece()
    {
        curPiece.setRandomShape();
        curX = BoardWidth / 2 + 1;
        curY = BoardHeight - 1 + curPiece.minY();

        if (!tryMove(curPiece, curX, curY)) {
            curPiece.setShape(Tetrominoes.NoShape);
            timer.stop();
            isStarted = false;
            statusbar.setText("game over");
        }
    }

    private boolean tryMove(Shape newPiece, int newX, int newY)
    {
        for (int i = 0; i < 4; ++i) {
            int x = newX + newPiece.x(i);
            int y = newY - newPiece.y(i);
            if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
                return false;
            if (shapeAt(x, y) != Tetrominoes.NoShape)
                return false;
        }

        curPiece = newPiece;
        curX = newX;
        curY = newY;
        repaint();
        return true;
    }

    private void removeFullLines()
    {
        int numFullLines = 0;

        for (int i = BoardHeight - 1; i >= 0; --i) {
            boolean lineIsFull = true;

            for (int j = 0; j < BoardWidth; ++j) {
                if (shapeAt(j, i) == Tetrominoes.NoShape) {
                    lineIsFull = false;
                    break;
                }
            }

            if (lineIsFull) {
                ++numFullLines;
                for (int k = i; k < BoardHeight - 1; ++k) {
                    for (int j = 0; j < BoardWidth; ++j)
                         board[(k * BoardWidth) + j] = shapeAt(j, k + 1);
                }
            }
        }

        if (numFullLines > 0) {
            numLinesRemoved += numFullLines;
            statusbar.setText(String.valueOf(numLinesRemoved));
            isFallingFinished = true;
            curPiece.setShape(Tetrominoes.NoShape);
            repaint();
        }
     }

    private void drawSquare(Graphics g, int x, int y, Tetrominoes shape)
    {
        Color colors[] = { new Color(0, 0, 0), new Color(204, 102, 102), 
            new Color(102, 204, 102), new Color(102, 102, 204), 
            new Color(204, 204, 102), new Color(204, 102, 204), 
            new Color(102, 204, 204), new Color(218, 170, 0)
        };


        Color color = colors[shape.ordinal()];

        g.setColor(color);
        g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);

        g.setColor(color.brighter());
        g.drawLine(x, y + squareHeight() - 1, x, y);
        g.drawLine(x, y, x + squareWidth() - 1, y);

        g.setColor(color.darker());
        g.drawLine(x + 1, y + squareHeight() - 1,
                         x + squareWidth() - 1, y + squareHeight() - 1);
        g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1,
                         x + squareWidth() - 1, y + 1);
    }

    class TAdapter extends KeyAdapter {
         public void keyPressed(KeyEvent e) {

             if (!isStarted || curPiece.getShape() == Tetrominoes.NoShape) {  
                 return;
             }

             int keycode = e.getKeyCode();

             if (keycode == 'p' || keycode == 'P') {
                 pause();
                 return;
             }

             if (isPaused)
                 return;

             switch (keycode) {
             case KeyEvent.VK_LEFT:
                 tryMove(curPiece, curX - 1, curY);
                 break;
             case KeyEvent.VK_RIGHT:
                 tryMove(curPiece, curX + 1, curY);
                 break;
             case KeyEvent.VK_DOWN:
                 tryMove(curPiece.rotateRight(), curX, curY);
                 break;
             case KeyEvent.VK_UP:
                 tryMove(curPiece.rotateLeft(), curX, curY);
                 break;
             case KeyEvent.VK_SPACE:
                 dropDown();
                 break;
             case 'd':
                 oneLineDown();
                 break;
             case 'D':
                 oneLineDown();
                 break;
             }

         }
     }
}
```
Finally, we have the Board.java file. This is where the game logic is located.


```
...

isFallingFinished = false;
isStarted = false;
isPaused = false;
numLinesRemoved = 0;
curX = 0;
curY = 0;
...
We initialize some important variables. The isFallingFinished variable determines if the tetris shape has finished falling and we then need to create a new shape. The numLinesRemoved counts the number of lines we have removed so far. The curX and curY variables determine the actual position of the falling tetris shape.

setFocusable(true);
We must explicitly call the setFocusable() method. From now, the board has the keyboard input.

timer = new Timer(400, this);
timer.start(); 
Timer object fires one or more action events after a specified delay. In our case, the timer calls the actionPerformed() method each 400 ms.

public void actionPerformed(ActionEvent e) {
    if (isFallingFinished) {
        isFallingFinished = false;
        newPiece();
    } else {
        oneLineDown();
    }
}
The actionPerformed() method checks if the falling has finished. If so, a new piece is created. If not, the falling tetris piece goes one line down.

Inside the paint() method, we draw the all objects on the board. The painting has two steps.

for (int i = 0; i < BoardHeight; ++i) {
    for (int j = 0; j < BoardWidth; ++j) {
        Tetrominoes shape = shapeAt(j, BoardHeight - i - 1);
        if (shape != Tetrominoes.NoShape)
            drawSquare(g, 0 + j * squareWidth(),
                    boardTop + i * squareHeight(), shape);
    }
}
In the first step we paint all the shapes, or remains of the shapes that have been dropped to the bottom of the board. All the squares are rememberd in the board array. We access it using the shapeAt() method.

if (curPiece.getShape() != Tetrominoes.NoShape) {
    for (int i = 0; i < 4; ++i) {
        int x = curX + curPiece.x(i);
        int y = curY - curPiece.y(i);
        drawSquare(g, 0 + x * squareWidth(),
                boardTop + (BoardHeight - y - 1) * squareHeight(),
                curPiece.getShape());
    }
}
In the second step, we paint the actual falling piece.

private void dropDown()
{
    int newY = curY;
    while (newY > 0) {
        if (!tryMove(curPiece, curX, newY - 1))
            break;
        --newY;
    }
    pieceDropped();
}
If we press the space key, the piece is dropped to the bottom. We simply try to drop the piece one line down until it reaches the bottom or the top of another fallen tetris piece.

private void clearBoard()
{
    for (int i = 0; i < BoardHeight * BoardWidth; ++i)
        board[i] = Tetrominoes.NoShape;
}
The clearBoard() method fills the board with empty NoSpapes. This is later used at collision detection.

private void pieceDropped()
{
    for (int i = 0; i < 4; ++i) {
        int x = curX + curPiece.x(i);
        int y = curY - curPiece.y(i);
        board[(y * BoardWidth) + x] = curPiece.getShape();
    }

    removeFullLines();

    if (!isFallingFinished)
        newPiece();
}
The pieceDropped() method puts the falling piece into the board array. Once again, the board holds all the squares of the pieces and remains of the pieces that has finished falling. When the piece has finished falling, it is time to check if we can remove some lines off the board. This is the job of the removeFullLines() method. Then we create a new piece. More precisely, we try to create a new piece.

private void newPiece()
{
    curPiece.setRandomShape();
    curX = BoardWidth / 2 + 1;
    curY = BoardHeight - 1 + curPiece.minY();

    if (!tryMove(curPiece, curX, curY)) {
        curPiece.setShape(Tetrominoes.NoShape);
        timer.stop();
        isStarted = false;
        statusbar.setText("game over");
    }
}
The newPiece() method creates a new tetris piece. The piece gets a new random shape. Then we compute the initial curX and curY values. If we cannot move to the initial positions, the game is over. We top out. The timer is stopped. We put game over string on the statusbar.

private boolean tryMove(Shape newPiece, int newX, int newY)
{
    for (int i = 0; i < 4; ++i) {
        int x = newX + newPiece.x(i);
        int y = newY - newPiece.y(i);
        if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
            return false;
        if (shapeAt(x, y) != Tetrominoes.NoShape)
            return false;
    }

    curPiece = newPiece;
    curX = newX;
    curY = newY;
    repaint();
    return true;
}
The tryMove() method tries to move the tetris piece. The method returns false if it has reached the board boundaries or it is adjacent to the already fallen tetris pieces.

for (int i = BoardHeight - 1; i >= 0; --i) {
    boolean lineIsFull = true;

    for (int j = 0; j < BoardWidth; ++j) {
        if (shapeAt(j, i) == Tetrominoes.NoShape) {
            lineIsFull = false;
            break;
        }
    }

    if (lineIsFull) {
        ++numFullLines;
        for (int k = i; k < BoardHeight - 1; ++k) {
            for (int j = 0; j < BoardWidth; ++j)
                board[(k * BoardWidth) + j] = shapeAt(j, k + 1);
        }
    }
}
Inside the removeFullLines() method, we check if there is any full row among all rows in the board. If there is at least one full line, it is removed. After finding a full line we increase the counter. We move all the lines above the full row one line down. This way we destroy the full line. Notice that in our Tetris game, we use so called naive gravity. This means that the squares may be left floating above empty gaps.

Every tetris piece has four squares. Each of the squares is drawn with the drawSquare() method. Tetris pieces have different colours.

g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);
The left and top sides of a square are drawn with a brighter colour. Similarly, the bottom and right sides are drawn with darker colours. This is to simulate a 3D edge.

We control the game with a keyboard. The control mechanism is implemented with a KeyAdapter. This is an inner class that overrides the keyPressed() method.

case KeyEvent.VK_RIGHT:
    tryMove(curPiece, curX + 1, curY);
    break;
```
If we pressed the left arrow key, we try to move the falling piece one square to the left.

*s12.postimg.org/esmjag7g9/tetris.png


5) Write a simple Minesweeper game in Java?

Minesweeper is a popular board game shipped with many operating systems by default. The goal of the game is to sweep all mines from a mine field. If the player clicks on the cell which contains a mine, the mine detonates and the game is over. Further a cell can contain a number or it can be blank. The number indicates how many mines are adjacent to this particular cell. We set a mark on a cell by right clicking on it. This way we indicate that we believe, there is a mine.

Board.java


```
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import java.util.Random;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class Board extends JPanel {

    private final int NUM_IMAGES = 13;
    private final int CELL_SIZE = 15;

    private final int COVER_FOR_CELL = 10;
    private final int MARK_FOR_CELL = 10;
    private final int EMPTY_CELL = 0;
    private final int MINE_CELL = 9;
    private final int COVERED_MINE_CELL = MINE_CELL + COVER_FOR_CELL;
    private final int MARKED_MINE_CELL = COVERED_MINE_CELL + MARK_FOR_CELL;

    private final int DRAW_MINE = 9;
    private final int DRAW_COVER = 10;
    private final int DRAW_MARK = 11;
    private final int DRAW_WRONG_MARK = 12;
    
    private final int N_MINES = 40;
    private final int N_ROWS = 16;
    private final int N_COLS = 16;

    private int[] field;
    private boolean inGame;
    private int mines_left;
    private Image[] img;

    private int all_cells;
    private JLabel statusbar;


    public Board(JLabel statusbar) {

        this.statusbar = statusbar;

        img = new Image[NUM_IMAGES];

        for (int i = 0; i < NUM_IMAGES; i++) {
            img[i] = (new ImageIcon(i + ".png")).getImage();
        }

        setDoubleBuffered(true);

        addMouseListener(new MinesAdapter());
        newGame();
    }


    private void newGame() {

        Random random;
        int current_col;

        int i = 0;
        int position = 0;
        int cell = 0;

        random = new Random();
        inGame = true;
        mines_left = N_MINES;

        all_cells = N_ROWS * N_COLS;
        field = new int[all_cells];
        
        for (i = 0; i < all_cells; i++)
            field[i] = COVER_FOR_CELL;

        statusbar.setText(Integer.toString(mines_left));


        i = 0;
        while (i < N_MINES) {

            position = (int) (all_cells * random.nextDouble());

            if ((position < all_cells) &&
                (field[position] != COVERED_MINE_CELL)) {


                current_col = position % N_COLS;
                field[position] = COVERED_MINE_CELL;
                i++;

                if (current_col > 0) { 
                    cell = position - 1 - N_COLS;
                    if (cell >= 0)
                        if (field[cell] != COVERED_MINE_CELL)
                            field[cell] += 1;
                    cell = position - 1;
                    if (cell >= 0)
                        if (field[cell] != COVERED_MINE_CELL)
                            field[cell] += 1;

                    cell = position + N_COLS - 1;
                    if (cell < all_cells)
                        if (field[cell] != COVERED_MINE_CELL)
                            field[cell] += 1;
                }

                cell = position - N_COLS;
                if (cell >= 0)
                    if (field[cell] != COVERED_MINE_CELL)
                        field[cell] += 1;
                cell = position + N_COLS;
                if (cell < all_cells)
                    if (field[cell] != COVERED_MINE_CELL)
                        field[cell] += 1;

                if (current_col < (N_COLS - 1)) {
                    cell = position - N_COLS + 1;
                    if (cell >= 0)
                        if (field[cell] != COVERED_MINE_CELL)
                            field[cell] += 1;
                    cell = position + N_COLS + 1;
                    if (cell < all_cells)
                        if (field[cell] != COVERED_MINE_CELL)
                            field[cell] += 1;
                    cell = position + 1;
                    if (cell < all_cells)
                        if (field[cell] != COVERED_MINE_CELL)
                            field[cell] += 1;
                }
            }
        }
    }


    public void find_empty_cells(int j) {

        int current_col = j % N_COLS;
        int cell;

        if (current_col > 0) { 
            cell = j - N_COLS - 1;
            if (cell >= 0)
                if (field[cell] > MINE_CELL) {
                    field[cell] -= COVER_FOR_CELL;
                    if (field[cell] == EMPTY_CELL)
                        find_empty_cells(cell);
                }

            cell = j - 1;
            if (cell >= 0)
                if (field[cell] > MINE_CELL) {
                    field[cell] -= COVER_FOR_CELL;
                    if (field[cell] == EMPTY_CELL)
                        find_empty_cells(cell);
                }

            cell = j + N_COLS - 1;
            if (cell < all_cells)
                if (field[cell] > MINE_CELL) {
                    field[cell] -= COVER_FOR_CELL;
                    if (field[cell] == EMPTY_CELL)
                        find_empty_cells(cell);
                }
        }

        cell = j - N_COLS;
        if (cell >= 0)
            if (field[cell] > MINE_CELL) {
                field[cell] -= COVER_FOR_CELL;
                if (field[cell] == EMPTY_CELL)
                    find_empty_cells(cell);
            }

        cell = j + N_COLS;
        if (cell < all_cells)
            if (field[cell] > MINE_CELL) {
                field[cell] -= COVER_FOR_CELL;
                if (field[cell] == EMPTY_CELL)
                    find_empty_cells(cell);
            }

        if (current_col < (N_COLS - 1)) {
            cell = j - N_COLS + 1;
            if (cell >= 0)
                if (field[cell] > MINE_CELL) {
                    field[cell] -= COVER_FOR_CELL;
                    if (field[cell] == EMPTY_CELL)
                        find_empty_cells(cell);
                }

            cell = j + N_COLS + 1;
            if (cell < all_cells)
                if (field[cell] > MINE_CELL) {
                    field[cell] -= COVER_FOR_CELL;
                    if (field[cell] == EMPTY_CELL)
                        find_empty_cells(cell);
                }

            cell = j + 1;
            if (cell < all_cells)
                if (field[cell] > MINE_CELL) {
                    field[cell] -= COVER_FOR_CELL;
                    if (field[cell] == EMPTY_CELL)
                        find_empty_cells(cell);
                }
        }
    }

    @Override
    public void paintComponent(Graphics g) {

        int cell = 0;
        int uncover = 0;

        for (int i = 0; i < N_ROWS; i++) {
            for (int j = 0; j < N_COLS; j++) {

                cell = field[(i * N_COLS) + j];

                if (inGame && cell == MINE_CELL)
                    inGame = false;

                if (!inGame) {
                    if (cell == COVERED_MINE_CELL) {
                        cell = DRAW_MINE;
                    } else if (cell == MARKED_MINE_CELL) {
                        cell = DRAW_MARK;
                    } else if (cell > COVERED_MINE_CELL) {
                        cell = DRAW_WRONG_MARK;
                    } else if (cell > MINE_CELL) {
                        cell = DRAW_COVER;
                    }


                } else {
                    if (cell > COVERED_MINE_CELL)
                        cell = DRAW_MARK;
                    else if (cell > MINE_CELL) {
                        cell = DRAW_COVER;
                        uncover++;
                    }
                }

                g.drawImage(img[cell], (j * CELL_SIZE),
                    (i * CELL_SIZE), this);
            }
        }

        if (uncover == 0 && inGame) {
            inGame = false;
            statusbar.setText("Game won");
        } else if (!inGame)
            statusbar.setText("Game lost");
    }


    class MinesAdapter extends MouseAdapter {
        
        @Override
        public void mousePressed(MouseEvent e) {

            int x = e.getX();
            int y = e.getY();

            int cCol = x / CELL_SIZE;
            int cRow = y / CELL_SIZE;

            boolean rep = false;


            if (!inGame) {
                newGame();
                repaint();
            }


            if ((x < N_COLS * CELL_SIZE) && (y < N_ROWS * CELL_SIZE)) {

                if (e.getButton() == MouseEvent.BUTTON3) {

                    if (field[(cRow * N_COLS) + cCol] > MINE_CELL) {
                        rep = true;

                        if (field[(cRow * N_COLS) + cCol] <= COVERED_MINE_CELL) {
                            if (mines_left > 0) {
                                field[(cRow * N_COLS) + cCol] += MARK_FOR_CELL;
                                mines_left--;
                                statusbar.setText(Integer.toString(mines_left));
                            } else
                                statusbar.setText("No marks left");
                        } else {

                            field[(cRow * N_COLS) + cCol] -= MARK_FOR_CELL;
                            mines_left++;
                            statusbar.setText(Integer.toString(mines_left));
                        }
                    }

                } else {

                    if (field[(cRow * N_COLS) + cCol] > COVERED_MINE_CELL) {
                        return;
                    }

                    if ((field[(cRow * N_COLS) + cCol] > MINE_CELL) &&
                        (field[(cRow * N_COLS) + cCol] < MARKED_MINE_CELL)) {

                        field[(cRow * N_COLS) + cCol] -= COVER_FOR_CELL;
                        rep = true;

                        if (field[(cRow * N_COLS) + cCol] == MINE_CELL)
                            inGame = false;
                        if (field[(cRow * N_COLS) + cCol] == EMPTY_CELL)
                            find_empty_cells((cRow * N_COLS) + cCol);
                    }
                }

                if (rep)
                    repaint();

            }
        }
    }
}
```
First we will define the constants used in our game.

private final int NUM_IMAGES = 13;
private final int CELL_SIZE = 15;
There are 13 images used in this game. A cell can be surrounded by maximum of 8 mines, so we need numbers 1..8. We need images for an empty cell, a mine, a covered cell, a marked cell and finally for a wrongly marked cell. The size of each of the images is 15x15px.

private final int COVER_FOR_CELL = 10;
private final int MARK_FOR_CELL = 10;
private final int EMPTY_CELL = 0;
...
A mine field is an array of numbers. For example 0 denotes an empty cell. Number 10 is used for a cell cover as well as for a mark. Using constants improves readability of the code.


```
private final int N_MINES = 40;
private final int N_ROWS = 16;
private final int N_COLS = 16;
The minefield in our game has 40 hidden mines. There are 16 rows and 16 columns in this field. So there are 256 cells together in the minefield.

private int[] field;
The field is an array of numbers. Each cell in the field has a specific number. E.g. a mine cell has number 9. A cell with number 2, meaning it is adjacent to two mines, has number two. The numbers are added. For example, a covered mine has number 19, 9 for the mine and 10 for the cell cover etc.

for (int i = 0; i < NUM_IMAGES; i++) {
    img[i] = (new ImageIcon(i + ".png")).getImage();
}
Here we load our images into the image array. The images are named 0.png, 1.png ... 12.png.

The newGame() initiates the Minesweeper game.

all_cells = N_ROWS * N_COLS;
field = new int[all_cells];

for (i = 0; i < all_cells; i++)
    field[i] = COVER_FOR_CELL;
These lines set up the mine field. Every cell is covered by default.

i = 0;
while (i < N_MINES) {

    position = (int) (all_cells * random.nextDouble());

    if ((position < all_cells) &&
        (field[position] != COVERED_MINE_CELL)) {


        current_col = position % N_COLS;
        field[position] = COVERED_MINE_CELL;
        i++;
        ...
In the while cycle we randomly position all mines in the field.

cell = position - cols;
if (cell >= 0)
    if (field[cell] != COVERED_MINE_CELL)
        field[cell] += 1;
Each of the cells can be surrounded up to 8 cells. (This does not apply to the border cells.) We raise the number for adjacent cells for each of the randomly placed mine. In our example, we add 1 to the top neighbor of the cell in question.

In the find_empty_cells() method, we find empty cells. If the player clicks on a mine cell, the game is over. If he clicks on a cell adjacent to a mine, he uncovers a number indicating how many mines the cell is adjacent to. Clicking on an empty cell leads to uncovering many other empty cells plus cells with a number that form a border around a space of empty borders. We use a recursive algorithm to find empty cells.

cell = j - 1;
if (cell >= 0)
    if (field[cell] > MINE_CELL) {
        field[cell] -= COVER_FOR_CELL;
        if (field[cell] == EMPTY_CELL)
            find_empty_cells(cell);
}
In this code, we check the cell that is left to an empty cell in question. If it is not empty, it is uncovered. If it is empty, we repeat the whole process by recursively calling the find_empty_cells() method.

The paintComponent() method turns numbers into images.

if (!inGame) {
    if (cell == COVERED_MINE_CELL) {
        cell = DRAW_MINE;
    } else if (cell == MARKED_MINE_CELL) {
        cell = DRAW_MARK;
    } else if (cell > COVERED_MINE_CELL) {
        cell = DRAW_WRONG_MARK;
    } else if (cell > MINE_CELL) {
        cell = DRAW_COVER;
    }
}
If the game is over and we lost, we show all uncovered mines if any and show all wrongly marked cells if any.

g.drawImage(img[cell], (j * CELL_SIZE),
    (i * CELL_SIZE), this);
This code line draws every cell on the window.

In the mousePressed() method we react to mouse clicks. The Minesweeper game is controlled solely by mouse. We react to left and right mouse clicks.

field[(cRow * N_COLS) + cCol] += MARK_FOR_CELL;
mines_left--;
If we right click on an unmarked cell, we add MARK_FOR_CELL to the number representing the cell. This leads to drawing a covered cell with a mark in the paintComponent() method.

if (field[(cRow * N_COLS) + cCol] > COVERED_MINE_CELL) {
    return;
}
Nothing happens if we click on the covered & marked cell. It must by first uncovered by another right click and only then it is possible to left click on it.

field[(cRow * N_COLS) + cCol] -= COVER_FOR_CELL;
A left click removes a cover from the cell.

if (field[(cRow * N_COLS) + cCol] == MINE_CELL)
    inGame = false;
if (field[(cRow * N_COLS) + cCol] == EMPTY_CELL)
    find_empty_cells((cRow * N_COLS) + cCol);                            
In case we left clicked on a mine, the game is over. If we left clicked on an empty cell, we call the find_empty_cells() method which recursively finds all adjacent empty cells.
```

Mines.java


```
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;


public class Mines extends JFrame {

    private final int FRAME_WIDTH = 250;
    private final int FRAME_HEIGHT = 290;

    private final JLabel statusbar;
    
    public Mines() {

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(FRAME_WIDTH, FRAME_HEIGHT);
        setLocationRelativeTo(null);
        setTitle("Minesweeper");

        statusbar = new JLabel("");
        add(statusbar, BorderLayout.SOUTH);

        add(new Board(statusbar));

        setResizable(false);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            
            @Override
            public void run() {                
                JFrame ex = new Mines();
                ex.setVisible(true);                
            }
        });
    }
}
```
This is the main class.

*s21.postimg.org/p5uk5d3yb/minesweeper.png


6) Write a simple Breakout game?

  The Breakout is an arcade game originally developed by Atari Inc. The game was created in 1976.

In this game, the player moves a paddle on the screen and bounces a ball or balls. The objective is to destroy bricks in the top of the window.


In our game, we have one paddle, one ball and 30 bricks. I have created an image for a ball, paddle and a brick in Inkscape. We use a timer to create a game cycle. We do not work with angles, we simply change directions. Top, bottom, left and right. I was inspired by the pybreakout game. It was developed in PyGame library by Nathan Dawson.

The game consists of seven files: Commons.java, Sprite.java, Ball.java, Paddle.java, Brick.java, Board.java, and Breakout.java.

Commons.java


```
public interface Commons {
    
    public static final int WIDTH = 300;
    public static final int HEIGTH = 400;
    public static final int BOTTOM_EDGE = 390;
    public static final int N_OF_BRICKS = 30;
    public static final int INIT_PADDLE_X = 200;
    public static final int INIT_PADDLE_Y = 360;
    public static final int INIT_BALL_X = 230;
    public static final int INIT_BALL_Y = 355;    
    public static final int DELAY = 1000;
    public static final int PERIOD = 10;
}
```
The Commons.java file has some common constants. The WIDTH and HEIGHT constants store the dimensions of the board. When the ball passes the BOTTOM_EDGE, the game is over. The N_OF_BRICKS is the number of bricks in the game. The INIT_PADDLE_X and INIT_PADDLE_Y are initial coordinates of the paddle object. The INIT_BALL_X and INIT_BALL_Y are initial coordinates of the ball object. The DELAY is the initial delay in milliseconds before task is to be executed and the PERIOD is the time in milliseconds between successive task executions that form game cycles.

Sprite.java


```
import java.awt.Image;
import java.awt.Rectangle;

public class Sprite {

    protected int x;
    protected int y;
    protected int i_width;
    protected int i_heigth;
    protected Image image;

    public void setX(int x) {
        this.x = x;
    }

    public int getX() {
        return x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getY() {
        return y;
    }

    public int getWidth() {
        return i_width;
    }

    public int getHeight() {
        return i_heigth;
    }

    Image getImage() {
        return image;
    }

    Rectangle getRect() {
        return new Rectangle(x, y,
                image.getWidth(null), image.getHeight(null));
    }
}
```
The Sprite class is a base class for all objects in the Board. We put here all methods and variables that are in Ball, Brick, and Paddle objects, like getImage() or getX() methods.

Brick.java


```
import javax.swing.ImageIcon;

public class Brick extends Sprite {

    private boolean destroyed;

    public Brick(int x, int y) {
        
        this.x = x;
        this.y = y;

        ImageIcon ii = new ImageIcon("brick.png");
        image = ii.getImage();

        i_width = image.getWidth(null);
        i_heigth = image.getHeight(null);

        destroyed = false;
    }

    public boolean isDestroyed() {
        
        return destroyed;
    }

    public void setDestroyed(boolean val) {
        
        destroyed = val;
    }
}
```
This is the Brick class.

private boolean destroyed;
In the destroyed variable we keep the state of a brick.

Ball.java


```
import javax.swing.ImageIcon;

public class Ball extends Sprite implements Commons {

    private int xdir;
    private int ydir;

    public Ball() {

        xdir = 1;
        ydir = -1;

        ImageIcon ii = new ImageIcon("ball.png");
        image = ii.getImage();

        i_width = image.getWidth(null);
        i_heigth = image.getHeight(null);

        resetState();
    }

    public void move() {
        
        x += xdir;
        y += ydir;

        if (x == 0) {
            setXDir(1);
        }

        if (x == WIDTH - i_width) {
            setXDir(-1);
        }

        if (y == 0) {
            setYDir(1);
        }
    }

    private void resetState() {
        
        x = INIT_BALL_X;
        y = INIT_BALL_Y;
    }

    public void setXDir(int x) {
        xdir = x;
    }

    public void setYDir(int y) {
        ydir = y;
    }

    public int getYDir() {
        return ydir;
    }
}
```
This is the Ball class.


```
public void move() {
    
    x += xdir;
    y += ydir;

    if (x == 0) {
        setXDir(1);
    }

    if (x == WIDTH - i_width) {
        setXDir(-1);
    }

    if (y == 0) {
        setYDir(1);
    }
}
```

The move() method moves the ball on the Board. If the ball hits the borders, the directions are changed accordingly.


```
public void setXDir(int x) {
    xdir = x;
}

public void setYDir(int y) {
    ydir = y;
}
```
These two methods are called when the ball hits the paddle or a brick.

Paddle.java


```
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;

public class Paddle extends Sprite implements Commons {

    private int dx;

    public Paddle() {

        ImageIcon ii = new ImageIcon("paddle.png");
        image = ii.getImage();

        i_width = image.getWidth(null);
        i_heigth = image.getHeight(null);

        resetState();
    }

    public void move() {

        x += dx;

        if (x <= 0) {
            x = 0;
        }

        if (x >= WIDTH - i_width) {
            x = WIDTH - i_width;
        }
    }

    public void keyPressed(KeyEvent e) {

        int key = e.getKeyCode();

        if (key == KeyEvent.VK_LEFT) {
            dx = -1;
        }

        if (key == KeyEvent.VK_RIGHT) {
            dx = 1;
        }
    }

    public void keyReleased(KeyEvent e) {

        int key = e.getKeyCode();

        if (key == KeyEvent.VK_LEFT) {
            dx = 0;
        }

        if (key == KeyEvent.VK_RIGHT) {
            dx = 0;
        }
    }

    private void resetState() {

        x = INIT_PADDLE_X;
        y = INIT_PADDLE_Y;
    }
}
```
This is the Paddle class. It encapsulates the paddle object in the Breakout game. The paddle is controlled with left and right arrow keys. By pressing the arrow key, we set the direction variable. By releasing the arrow key, we set the dx variable to zero. This way the paddle stops moving.


```
public void move() {

    x += dx;

    if (x <= 0) {
        x = 0;
    }

    if (x >= WIDTH - i_width) {
        x = WIDTH - i_width;
    }
}
```

The paddle moves only in the horizontal direction, so we only update the x coordinate. The if conditions ensure that the paddle does not pass the window edges.

Board.java


```
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;

public class Board extends JPanel implements Commons {

    private Timer timer;
    private String message = "Game Over";
    private Ball ball;
    private Paddle paddle;
    private Brick bricks[];
    private boolean ingame = true;

    public Board() {

        initBoard();
    }

    private void initBoard() {

        addKeyListener(new TAdapter());
        setFocusable(true);

        bricks = new Brick[N_OF_BRICKS];
        setDoubleBuffered(true);
        timer = new Timer();
        timer.scheduleAtFixedRate(new ScheduleTask(), DELAY, PERIOD);
    }

    @Override
    public void addNotify() {

        super.addNotify();
        gameInit();
    }

    private void gameInit() {

        ball = new Ball();
        paddle = new Paddle();

        int k = 0;
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 6; j++) {
                bricks[k] = new Brick(j * 40 + 30, i * 10 + 50);
                k++;
            }
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);

        if (ingame) {
            
            drawObjects(g2d);
        } else {

            gameFinished(g2d);
        }

        Toolkit.getDefaultToolkit().sync();
    }
    
    private void drawObjects(Graphics2D g2d) {
        
        g2d.drawImage(ball.getImage(), ball.getX(), ball.getY(),
                ball.getWidth(), ball.getHeight(), this);
        g2d.drawImage(paddle.getImage(), paddle.getX(), paddle.getY(),
                paddle.getWidth(), paddle.getHeight(), this);

        for (int i = 0; i < N_OF_BRICKS; i++) {
            if (!bricks[i].isDestroyed()) {
                g2d.drawImage(bricks[i].getImage(), bricks[i].getX(),
                        bricks[i].getY(), bricks[i].getWidth(),
                        bricks[i].getHeight(), this);
            }
        }
    }
    
    private void gameFinished(Graphics2D g2d) {

        Font font = new Font("Verdana", Font.BOLD, 18);
        FontMetrics metr = this.getFontMetrics(font);

        g2d.setColor(Color.BLACK);
        g2d.setFont(font);
        g2d.drawString(message,
                (Commons.WIDTH - metr.stringWidth(message)) / 2,
                Commons.WIDTH / 2);
    }

    private class TAdapter extends KeyAdapter {

        @Override
        public void keyReleased(KeyEvent e) {
            paddle.keyReleased(e);
        }

        @Override
        public void keyPressed(KeyEvent e) {
            paddle.keyPressed(e);
        }
    }

    private class ScheduleTask extends TimerTask {

        @Override
        public void run() {

            ball.move();
            paddle.move();
            checkCollision();
            repaint();
        }
    }

    private void stopGame() {

        ingame = false;
        timer.cancel();
    }

    private void checkCollision() {

        if (ball.getRect().getMaxY() > Commons.BOTTOM_EDGE) {
            stopGame();
        }

        for (int i = 0, j = 0; i < N_OF_BRICKS; i++) {
            
            if (bricks[i].isDestroyed()) {
                j++;
            }
            
            if (j == N_OF_BRICKS) {
                message = "Victory";
                stopGame();
            }
        }

        if ((ball.getRect()).intersects(paddle.getRect())) {

            int paddleLPos = (int) paddle.getRect().getMinX();
            int ballLPos = (int) ball.getRect().getMinX();

            int first = paddleLPos + 8;
            int second = paddleLPos + 16;
            int third = paddleLPos + 24;
            int fourth = paddleLPos + 32;

            if (ballLPos < first) {
                ball.setXDir(-1);
                ball.setYDir(-1);
            }

            if (ballLPos >= first && ballLPos < second) {
                ball.setXDir(-1);
                ball.setYDir(-1 * ball.getYDir());
            }

            if (ballLPos >= second && ballLPos < third) {
                ball.setXDir(0);
                ball.setYDir(-1);
            }

            if (ballLPos >= third && ballLPos < fourth) {
                ball.setXDir(1);
                ball.setYDir(-1 * ball.getYDir());
            }

            if (ballLPos > fourth) {
                ball.setXDir(1);
                ball.setYDir(-1);
            }
        }

        for (int i = 0; i < N_OF_BRICKS; i++) {
            
            if ((ball.getRect()).intersects(bricks[i].getRect())) {

                int ballLeft = (int) ball.getRect().getMinX();
                int ballHeight = (int) ball.getRect().getHeight();
                int ballWidth = (int) ball.getRect().getWidth();
                int ballTop = (int) ball.getRect().getMinY();

                Point pointRight = new Point(ballLeft + ballWidth + 1, ballTop);
                Point pointLeft = new Point(ballLeft - 1, ballTop);
                Point pointTop = new Point(ballLeft, ballTop - 1);
                Point pointBottom = new Point(ballLeft, ballTop + ballHeight + 1);

                if (!bricks[i].isDestroyed()) {
                    if (bricks[i].getRect().contains(pointRight)) {
                        ball.setXDir(-1);
                    } else if (bricks[i].getRect().contains(pointLeft)) {
                        ball.setXDir(1);
                    }

                    if (bricks[i].getRect().contains(pointTop)) {
                        ball.setYDir(1);
                    } else if (bricks[i].getRect().contains(pointBottom)) {
                        ball.setYDir(-1);
                    }

                    bricks[i].setDestroyed(true);
                }
            }
        }
    }
}
```

This is the Board class. Here we put the game logic.


```
public void gameInit() {

    ball = new Ball();
    paddle = new Paddle();

    int k = 0;
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 6; j++) {
            bricks[k] = new Brick(j * 40 + 30, i * 10 + 50);
            k++;
        }
    }
}
In the gameInit() method we create a ball, a paddle, and thirty bricks.

if (ingame) {
    
    drawObjects(g2d);
} else {

    gameFinished(g2d);
}
Depending on the ingame variable, we either draw all the objects in the drawObjects() method or finish the game with the gameFinished() method.

private void drawObjects(Graphics2D g2d) {
    
    g2d.drawImage(ball.getImage(), ball.getX(), ball.getY(),
            ball.getWidth(), ball.getHeight(), this);
    g2d.drawImage(paddle.getImage(), paddle.getX(), paddle.getY(),
            paddle.getWidth(), paddle.getHeight(), this);

    for (int i = 0; i < N_OF_BRICKS; i++) {
        if (!bricks[i].isDestroyed()) {
            g2d.drawImage(bricks[i].getImage(), bricks[i].getX(),
                    bricks[i].getY(), bricks[i].getWidth(),
                    bricks[i].getHeight(), this);
        }
    }
}
The drawObjects() method draws all the objects of the game. The sprites are drawn with the drawImage() method.

private void gameFinished(Graphics2D g2d) {

    Font font = new Font("Verdana", Font.BOLD, 18);
    FontMetrics metr = this.getFontMetrics(font);

    g2d.setColor(Color.BLACK);
    g2d.setFont(font);
    g2d.drawString(message,
            (Commons.WIDTH - metr.stringWidth(message)) / 2,
            Commons.WIDTH / 2);
}
The gameFinished() method draws "Game over" or "Victory" to the middle of the window.

private class ScheduleTask extends TimerTask {

    @Override
    public void run() {

        ball.move();
        paddle.move();
        checkCollision();
        repaint();
    }
}
The ScheduleTask is triggerd every PERIOD ms. In its run() method, we move the ball and the paddle. We check for possible collisions and repaint the screen.

if (ball.getRect().getMaxY() > Commons.BOTTOM_EDGE) {
    stopGame();
}
If the ball hits the bottom, we stop the game.

for (int i = 0, j = 0; i < N_OF_BRICKS; i++) {
    
    if (bricks[i].isDestroyed()) {
        j++;
    }
    
    if (j == N_OF_BRICKS) {
        message = "Victory";
        stopGame();
    }
}
We check how many bricks are destroyed. If we destroyed all N_OF_BRICKS bricks, we win the game.

if (ballLPos < first) {
    ball.setXDir(-1);
    ball.setYDir(-1);
}
If the ball hits the first part of the paddle, we change the direction of the ball to the north-west.

if (bricks[i].getRect().contains(pointTop)) {
    ball.setYDir(1);
}
```

If the ball hits the bottom of the brick, we change the y direction of the ball; it goes down.

Breakout.java


```
import java.awt.EventQueue;
import javax.swing.JFrame;

public class Breakout extends JFrame {

    public Breakout() {
        
        initUI();
    }
    
    private void initUI() {
        
        add(new Board());
        setTitle("Breakout");
        
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(Commons.WIDTH, Commons.HEIGTH);
        setLocationRelativeTo(null);
        setResizable(false);
        setVisible(true);
    }

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {                
                Breakout game = new Breakout();
                game.setVisible(true);                
            }
        });
    }
}
```

*s14.postimg.org/b3g7mcxst/breakout.png

7) Write a simple Scrabblet game in Java?


```
//  Bag.java
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
import java.util.Random;

class Bag {

    private Random rand;
    private int letter_counts[] = {
        2, 9, 2, 2, 4, 12, 2, 3, 2, 9, 1, 1, 4, 2,
        6, 8, 2, 1, 6, 4, 6, 4, 2, 2, 1, 2, 1
    };
    private int letter_points[] = {
        0, 1, 3, 3, 2, 1, 4, 2, 4, 1, 8, 5, 1, 3,
        1, 1, 3, 10, 1, 1, 1, 1, 4, 4, 8, 4, 10
    };
    private Letter letters[] = new Letter[100];
    private int n = 0;

    Bag(int seed) {
        rand = new Random(seed);
        for (int i = 0; i < letter_counts.length; i++) {
            for (int j = 0; j < letter_counts[i]; j++) {
                Letter l = new Letter(i == 0 ? '*' : (char) ('A' + i - 1),
                        letter_points[i]);
                putBack(l);
            }
        }
    }

    synchronized Letter takeOut() {
        if (n == 0) {
            return null;
        }
        int i = (int) (rand.nextDouble() * n);
        Letter l = letters[i];
        if (i != n - 1) {
            System.arraycopy(letters, i + 1, letters, i, n - i - 1);
        }
        n--;
        return l;
    }

    synchronized void putBack(Letter l) {
        letters[n++] = l;
    }
}
//  Board.java
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
import java.awt.*;
import java.awt.event.*;

class Board extends Canvas {

    private Letter board[][] = new Letter[15][15];
    private Letter tray[] = new Letter[7];
    private Point orig = new Point(0, 0);
    private Point here = new Point(0, 0);
    private String name;
    private int total_score = 0;
    private int turn_score = 0;
    private int others_score = 0;
    private String others_name = null;

    Board(String our_name, String other_name) {
        name = our_name;
        others_name = other_name;
        addMouseListener(new MyMouseAdapter());
        addMouseMotionListener(new MyMouseMotionAdapter());
    }

    Board() {
        addMouseListener(new MyMouseAdapter());
        addMouseMotionListener(new MyMouseMotionAdapter());
    }

    void othersTurn(int score) {
        others_score += score;
        paintScore();
        repaint();
    }

    int getTurnScore() {
        paintScore();
        return turn_score;
    }

    Letter getTray(int i) {
        return tray[i];
    }

    synchronized boolean addLetter(Letter l) {
        for (int i = 0; i < 7; i++) {
            if (tray[i] == null) {
                tray[i] = l;
                moveLetter(l, i, 15);
                return true;
            }
        }
        return false;
    }

    private boolean existingLetterAt(int x, int y) {
        Letter l = null;
        return (x >= 0 && x <= 14 && y >= 0 && y <= 14
                && (l = board[y][x]) != null && l.recall() == null);
    }

    synchronized String findwords() {
        String res = "";
        turn_score = 0;

        int ntiles = 0;
        Letter atplay[] = new Letter[7];
        for (int i = 0; i < 7; i++) {
            if (tray[i] != null && tray[i].recall() != null) {
                atplay[ntiles++] = tray[i];
            }
        }
        if (ntiles == 0) {
            return res;
        }

        boolean horizontal = true; // if there's one tile,
        // call it horizontal
        boolean vertical = false;
        if (ntiles > 1) {
            int x = atplay[0].x;
            int y = atplay[0].y;
            horizontal = atplay[1].y == y;
            vertical = atplay[1].x == x;
            if (!horizontal && !vertical) // diagonal...
            {
                return null;
            }
            for (int i = 2; i < ntiles; i++) {
                if (horizontal && atplay[i].y != y
                        || vertical && atplay[i].x != x) {
                    return null;
                }
            }
        }
        // make sure that at least one played tile is
        // touching at least one existing tile.
        boolean attached = false;
        for (int i = 0; i < ntiles; i++) {
            Point p = atplay[i].recall();
            int x = p.x;
            int y = p.y;
            if ((x == 7 && y == 7 && ntiles > 1)
                    || existingLetterAt(x - 1, y) || existingLetterAt(x + 1, y)
                    || existingLetterAt(x, y - 1) || existingLetterAt(x, y + 1)) {
                attached = true;
                break;
            }
        }
        if (!attached) {
            return null;
        }
        // we use -1 to mean check the major direction first
        // then 0..ntiles checks for words orthogonal to it.
        for (int i = -1; i < ntiles; i++) {
            Point p = atplay[i == -1 ? 0 : i].recall(); // where is it?
            int x = p.x;
            int y = p.y;

            int xinc, yinc;
            if (horizontal) {
                xinc = 1;
                yinc = 0;
            } else {
                xinc = 0;
                yinc = 1;
            }
            int mult = 1;

            String word = "";
            int word_score = 0;
            // here we back up to the top/left-most letter
            while (x >= xinc && y >= yinc
                    && board[y - yinc][x - xinc] != null) {
                x -= xinc;
                y -= yinc;
            }

            int n = 0;
            int letters_seen = 0; // letters we've just played.
            Letter l;
            while (x < 15 && y < 15 && (l = board[y][x]) != null) {
                word += l.getSymbol();
                int lscore = l.getPoints();
                if (l.recall() != null) {  // one we just played...
                    Color t = tiles[y < 8 ? y : 14 - y][x < 8 ? x : 14 - x];
                    if (t == w3) {
                        mult *= 3;
                    } else if (t == w2) {
                        mult *= 2;
                    } else if (t == l3) {
                        lscore *= 3;
                    } else if (t == l2) {
                        lscore *= 2;
                    }
                    if (i == -1) {
                        letters_seen++;
                    }
                }
                word_score += lscore;
                n++;
                x += xinc;
                y += yinc;
            }
            word_score *= mult;
            if (i == -1) {     // first pass...

                // if we didn't see all the letters, then there was a gap,
                // which is an illegal tile position.
                if (letters_seen != ntiles) {
                    return null;
                }

                if (ntiles == 7) {
                    turn_score += 50;
                }

                // after the first pass, switch to looking the other way.
                horizontal = !horizontal;
            }
            if (n < 2) // don't count single letters twice.
            {
                continue;
            }

            turn_score += word_score;
            res += word + " ";
        }
        total_score += turn_score;
        return res;
    }

    synchronized void commit(ServerConnection s) {
        for (int i = 0; i < 7; i++) {
            Point p;
            if (tray[i] != null && (p = tray[i].recall()) != null) {
                if (s != null) // there's a server connection
                {
                    s.move(tray[i].getSymbol(), p.x, p.y);
                }
                commitLetter(tray[i]);  // marks this as not in play.
                tray[i] = null;
            }
        }
    }

    void commitLetter(Letter l) {
        if (l != null && l.recall() != null) {
            l.paint(offGraphics, Letter.DIM);
            l.remember(null);   // marks this as not in play.
        }
    }
    private Letter pick;  // the letter being dragged around.
    private int dx, dy;   // offset to topleft corner of pick.
    private int lw, lh;   // letter width and height.
    private int tm, lm;   // top and left margin.
    private int lt;       // line thickness (between tiles).
    private int aw, ah;   // letter area size.

    private Dimension offscreensize;
    private Image offscreen;
    private Graphics offGraphics;
    private Image offscreen2;
    private Graphics offGraphics2;

    public void update(Graphics g) {
        paint(g);
    }

    public synchronized void paint(Graphics g) {
        Dimension d = checksize();
        Graphics gc = offGraphics2;
        if (pick != null) {
            gc = gc.create();
            gc.clipRect(x0, y0, w0, h0);
            g.clipRect(x0, y0, w0, h0);
        }
        gc.drawImage(offscreen, 0, 0, null);

        for (int i = 0; i < 7; i++) {
            Letter l = tray[i];
            if (l != null && l != pick) {
                l.paint(gc, Letter.NORMAL);
            }
        }
        if (pick != null) {
            pick.paint(gc, Letter.BRIGHT);
        }

        g.drawImage(offscreen2, 0, 0, null);
    }

    Letter LetterHit(int x, int y) {
        for (int i = 0; i < 7; i++) {
            if (tray[i] != null && tray[i].hit(x, y)) {
                return tray[i];
            }
        }
        return null;
    }

    private void unplay(Letter let) {
        Point p = let.recall();
        if (p != null) {
            board[p.y][p.x] = null;
            let.remember(null);
        }
    }

    private void moveToTray(Letter l, int i) {
        int x = lm + (lw + lt) * i;
        int y = tm + ah - 2 * lt;
        l.move(x, y);
    }

    private void dropOnTray(Letter l, int x) {
        unplay(l); // unhook where we were.

        // find out what slot this letter WAS in.
        int oldx = 0;
        for (int i = 0; i < 7; i++) {
            if (tray[i] == l) {
                oldx = i;
                break;
            }
        }

        // if the slot we dropped on was empty,
        // find the rightmost occupied slot.
        if (tray[x] == null) {
            for (int i = 6; i >= 0; i--) {
                if (tray[i] != null) {
                    x = i;
                    break;
                }
            }
        }
        // if the slot we dropped on was from a tile already
        // played on the board, just swap slots with it.
        if (tray[x].recall() != null) {
            tray[oldx] = tray[x];
        } else {
            // we are just rearranging a tile already on the tray.
            if (oldx < x) {   // shuffle left.
                for (int i = oldx; i < x; i++) {
                    tray[i] = tray[i + 1];
                    if (tray[i].recall() == null) {
                        moveToTray(tray[i], i);
                    }
                }
            } else {          // shuffle right.
                for (int i = oldx; i > x; i--) {
                    tray[i] = tray[i - 1];
                    if (tray[i].recall() == null) {
                        moveToTray(tray[i], i);
                    }
                }
            }
        }
        tray[x] = l;
        moveToTray(l, x);
    }

    Letter getLetter(int x, int y) {
        return board[y][x];
    }

    void moveLetter(Letter l, int x, int y) {
        if (y > 14 || x > 14 || y < 0 || x < 0) {
            // if we are off the board.
            if (x > 6) {
                x = 6;
            }
            if (x < 0) {
                x = 0;
            }
            dropOnTray(l, x);
        } else {
            if (board[y][x] != null) {
                x = orig.x;
                y = orig.y;
            } else {
                here.x = x;
                here.y = y;
                unplay(l);
                board[y][x] = l;
                l.remember(here);

                // turn it back into pixels
                x = lm + (lw + lt) * x;
                y = tm + (lh + lt) * y;
            }
            l.move(x, y);
        }
    }
    private Color bg = new Color(175, 185, 175);
    private Color w3 = new Color(255, 50, 100);
    private Color w2 = new Color(255, 200, 200);
    private Color l3 = new Color(75, 75, 255);
    private Color l2 = new Color(150, 200, 255);
    private Color tiles[][] = {
        {w3, bg, bg, l2, bg, bg, bg, w3},
        {bg, w2, bg, bg, bg, l3, bg, bg},
        {bg, bg, w2, bg, bg, bg, l2, bg},
        {l2, bg, bg, w2, bg, bg, bg, l2},
        {bg, bg, bg, bg, w2, bg, bg, bg},
        {bg, l3, bg, bg, bg, l3, bg, bg},
        {bg, bg, l2, bg, bg, bg, l2, bg},
        {w3, bg, bg, l2, bg, bg, bg, w2}
    };

    private Dimension checksize() {
        Dimension d = getSize();
        int w = d.width;
        int h = d.height;

        if (w < 1 || h < 1) {
            return d;
        }
        if ((offscreen == null)
                || (w != offscreensize.width)
                || (h != offscreensize.height)) {
            System.out.println("updating board: " + w + " x " + h + "\r");

            offscreen = createImage(w, h);
            offscreensize = d;
            offGraphics = offscreen.getGraphics();
            offscreen2 = createImage(w, h);
            offGraphics2 = offscreen2.getGraphics();

            offGraphics.setColor(Color.white);
            offGraphics.fillRect(0, 0, w, h);

            // lt is the thickness of the white lines between tiles.
            // gaps is the sum of all the whitespace.
            // lw, lh = is the dimensions of the tiles.
            // aw, ah is the dimensions of the entire board
            // lm, tm is the left and top margin to center aw, ah in the applet.
            lt = 1 + w / 400;
            int gaps = lt * 20;

            lw = (w - gaps) / 15;
            lh = (h - gaps - lt * 2) / 16;    // compensating for tray height;
            aw = lw * 15 + gaps;
            ah = lh * 15 + gaps;
            lm = (w - aw) / 2 + lt;
            tm = (h - ah - (lt * 2 + lh)) / 2 + lt;

            offGraphics.setColor(Color.black);
            offGraphics.fillRect(lm, tm, aw - 2 * lt, ah - 2 * lt);
            lm += lt;
            tm += lt;
            offGraphics.setColor(Color.white);
            offGraphics.fillRect(lm, tm, aw - 4 * lt, ah - 4 * lt);
            lm += lt;
            tm += lt;
            int sfh = (lh > 30) ? lh / 4 : lh / 2;
            Font font = new Font("SansSerif", Font.PLAIN, sfh);
            offGraphics.setFont(font);
            for (int j = 0, y = tm; j < 15; j++, y += lh + lt) {
                for (int i = 0, x = lm; i < 15; i++, x += lw + lt) {
                    Color c = tiles[j < 8 ? j : 14 - j][i < 8 ? i : 14 - i];
                    offGraphics.setColor(c);
                    offGraphics.fillRect(x, y, lw, lh);
                    offGraphics.setColor(Color.black);
                    if (lh > 30) {
                        String td = (c == w2 || c == l2) ? "DOUBLE"
                                : (c == w3 || c == l3) ? "TRIPLE" : null;
                        String wl = (c == l2 || c == l3) ? "LETTER"
                                : (c == w2 || c == w3) ? "WORD" : null;
                        if (td != null) {
                            center(offGraphics, td, x, y + 2 + sfh, lw);
                            center(offGraphics, wl, x, y + 2 * (2 + sfh), lw);
                            center(offGraphics, "SCORE", x, y + 3 * (2 + sfh), lw);
                        }
                    } else {
                        String td = (c == w2 || c == l2) ? "2"
                                : (c == w3 || c == l3) ? "3" : null;
                        String wl = (c == l2 || c == l3) ? "L"
                                : (c == w2 || c == w3) ? "W" : null;
                        if (td != null) {
                            center(offGraphics, td + wl, x,
                                    y + (lh - sfh) * 4 / 10 + sfh, lw);
                        }
                    }
                }
            }
            Color c = new Color(255, 255, 200);
            offGraphics.setColor(c);
            offGraphics.fillRect(lm, tm + ah - 3 * lt, 7 * (lw + lt), lh + 2 * lt);

            Letter.resize(lw, lh);

            // if we already have some letters, place them.
            for (int i = 0; i < 7; i++) {
                if (tray[i] != null) {
                    moveToTray(tray[i], i);
                }
            }
            paintScore();
        }
        return d;
    }

    private void center(Graphics g, String s, int x, int y, int w) {
        x += (w - g.getFontMetrics().stringWidth(s)) / 2;
        g.drawString(s, x, y);
    }

    private void paintScore() {
        int x = lm + (lw + lt) * 7 + lm;
        int y = tm + ah - 3 * lt;
        int h = lh + 2 * lt;
        Font font = new Font("TimesRoman", Font.PLAIN, h / 2);
        offGraphics.setFont(font);
        FontMetrics fm = offGraphics.getFontMetrics();

        offGraphics.setColor(Color.white);
        offGraphics.fillRect(x, y, aw, h);
        offGraphics.setColor(Color.black);
        if (others_name == null) {
            int y0 = (h - fm.getHeight()) / 2 + fm.getAscent();
            offGraphics.drawString("Score: " + total_score, x, y + y0);
        } else {
            h /= 2;
            int y0 = (h - fm.getHeight()) / 2 + fm.getAscent();
            offGraphics.drawString(name + ": " + total_score, x, y + y0);
            offGraphics.drawString(others_name + ": " + others_score, x, y + h + y0);
        }
    }

    private int x0, y0, w0, h0;

    private void selectLetter(int x, int y) {
        pick = LetterHit(x, y);
        if (pick != null) {
            dx = pick.x - x;
            dy = pick.y - y;
            orig.x = pick.x;
            orig.y = pick.y;
        }
        repaint();
    }

    private void dropLetter(int x, int y) {
        if (pick != null) {
            // find the center of the tile
            x += dx + lw / 2;
            y += dy + lh / 2;
            // find the tile index
            x = (x - lm) / (lw + lt);
            y = (y - tm) / (lh + lt);

            moveLetter(pick, x, y);

            pick = null;
            repaint();
        }
    }

    private void dragLetter(int x, int y) {
        if (pick != null) {
            int ox = pick.x;
            int oy = pick.y;
            pick.move(x + dx, y + dy);
            x0 = Math.min(ox, pick.x);
            y0 = Math.min(oy, pick.y);
            w0 = pick.w + Math.abs(ox - pick.x);
            h0 = pick.h + Math.abs(oy - pick.y);
            paint(getGraphics());
        }
    }

    class MyMouseAdapter extends MouseAdapter {

        public void mousePressed(MouseEvent me) {
            selectLetter(me.getX(), me.getY());
        }

        public void mouseReleased(MouseEvent me) {
            dropLetter(me.getX(), me.getY());
        }
    }

    class MyMouseMotionAdapter extends MouseMotionAdapter {

        public synchronized void mouseDragged(MouseEvent me) {
            dragLetter(me.getX(), me.getY());
        }
    }
}
//  ClientConnection.java
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
import java.io.*;
import java.net.*;
import java.util.*;

class ClientConnection implements Runnable {

    private Socket sock;
    private BufferedReader in;
    private OutputStream out;
    private String host;
    private Server server;
    private static final String CRLF = "\r\n";
    private String name = null;    // for humans
    private String id;
    private boolean busy = false;

    public ClientConnection(Server srv, Socket s, int i) {
        try {
            server = srv;
            sock = s;
            in = new BufferedReader(new InputStreamReader(s.getInputStream()));
            out = s.getOutputStream();
            host = s.getInetAddress().getHostName();
            id = "" + i;

            // tell the new one who it is...
            write("id " + id + CRLF);

            new Thread(this).start();
        } catch (IOException e) {
            System.out.println("failed ClientConnection " + e);
        }
    }

    public String toString() {
        return id + " " + host + " " + name;
    }

    public String getHost() {
        return host;
    }

    public String getId() {
        return id;
    }

    public boolean isBusy() {
        return busy;
    }

    public void setBusy(boolean b) {
        busy = b;
    }

    public void close() {
        server.kill(this);
        try {
            sock.close();   // closes in and out too.
        } catch (IOException e) {
        }
    }

    public void write(String s) {
        byte buf[];
        buf = s.getBytes();
        try {
            out.write(buf, 0, buf.length);
        } catch (IOException e) {
            close();
        }
    }

    private String readline() {
        try {
            return in.readLine();
        } catch (IOException e) {
            return null;
        }
    }
    static private final int NAME = 1;
    static private final int QUIT = 2;
    static private final int TO = 3;
    static private final int DELETE = 4;

    static private Hashtable<String, Integer> keys = new Hashtable<>();
    static private String keystrings[] = {
        "", "name", "quit", "to", "delete"
    };

    static {
        for (int i = 0; i < keystrings.length; i++) {
            keys.put(keystrings[i], new Integer(i));
        }
    }

    private int lookup(String s) {
        Integer i = keys.get(s);
        return i == null ? -1 : i.intValue();
    }

    @Override
    public void run() {
        String s;
        StringTokenizer st;

        while ((s = readline()) != null) {
            st = new StringTokenizer(s);
            String keyword = st.nextToken();
            switch (lookup(keyword)) {
                default:
                    System.out.println("bogus keyword: " + keyword + "\r");
                    break;
                case NAME:
                    name = st.nextToken()
                            + (st.hasMoreTokens() ? " " + st.nextToken(CRLF) : "");
                    System.out.println("[" + new Date() + "] " + this + "\r");
                    server.set(id, this);
                    break;
                case QUIT:
                    close();
                    return;
                case TO:
                    String dest = st.nextToken();
                    String body = st.nextToken(CRLF);
                    server.sendto(dest, body);
                    break;
                case DELETE:
                    busy = true;
                    server.delete(id);
                    break;
            }
        }
        close();
    }
}
//  IntroCanvas.java
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
import java.awt.*;
import java.awt.event.*;

class IntroCanvas extends Canvas {

    private Color pink = new Color(255, 200, 200);
    private Color blue = new Color(150, 200, 255);
    private Color yellow = new Color(250, 220, 100);

    private int w, h;
    private int edge = 16;
    private static final String title = "Scrabblet";
    private static final String name
            = "Copyright 1999 - Patrick Naughton";
    private static final String book
            = "Chapter 31 from 'Java: The Complete Reference'";
    private Font namefont, titlefont, bookfont;

    IntroCanvas() {
        setBackground(yellow);
        titlefont = new Font("SansSerif", Font.BOLD, 58);
        namefont = new Font("SansSerif", Font.BOLD, 18);
        bookfont = new Font("SansSerif", Font.PLAIN, 12);
        addMouseListener(new MyMouseAdapter());
    }

    private void d(Graphics g, String s, Color c, Font f, int y, int off) {
        g.setFont(f);
        FontMetrics fm = g.getFontMetrics();
        g.setColor(c);
        g.drawString(s, (w - fm.stringWidth(s)) / 2 + off, y + off);
    }

    @Override
    public void paint(Graphics g) {
        Dimension d = getSize();
        w = d.width;
        h = d.height;
        g.setColor(blue);
        g.fill3DRect(edge, edge, w - 2 * edge, h - 2 * edge, true);
        d(g, title, Color.black, titlefont, h / 2, 1);
        d(g, title, Color.white, titlefont, h / 2, -1);
        d(g, title, pink, titlefont, h / 2, 0);
        d(g, name, Color.black, namefont, h * 3 / 4, 0);
        d(g, book, Color.black, bookfont, h * 7 / 8, 0);
    }

    class MyMouseAdapter extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent me) {
            ((Frame) getParent()).setVisible(false);
        }
    }
}
//  Letter.java
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
import java.awt.*;

class Letter {

    static int w, h;
    private static Font font, smfont;
    private static int y0, ys0;
    private static int lasth = -1;
    static final int NORMAL = 0;
    static final int DIM = 1;
    static final int BRIGHT = 2;
    private static Color colors[][] = {
        mix(250, 220, 100), // normal
        mix(200, 150, 80), // dim
        mix(255, 230, 150) // bright
    };

    private static Color mix (int r, int g, int b)
        [] {
    Color arr[] = new Color[3];

        arr[NORMAL] = new Color(r, g, b);
        arr[DIM] = gain(arr[0], .71);
        arr[BRIGHT] = gain(arr[0], 1.31);
        return arr;
    }

    private static int clamp(double d) {
        return (d < 0) ? 0 : ((d > 255) ? 255 : (int) d);
    }

    private static Color gain(Color c, double f) {
        return new Color(
                clamp(c.getRed() * f),
                clamp(c.getGreen() * f),
                clamp(c.getBlue() * f));
    }
    private boolean valid = false;

    // quantized tile position of Letter. (just stored here).
    private Point tile = null;
    int x, y;               // position of Letter.
    private int x0;         // offset of symbol on tile.
    private int w0;         // width in pixels of symbol.
    private int xs0;        // offset of points on tile.
    private int ws0;        // width in pixels of points.
    private int gap = 1;    // pixels between symbol and points.
    private String symbol;
    private int points;

    Letter(char s, int p) {
        symbol = "" + s;
        points = p;
    }

    String getSymbol() {
        return symbol;
    }

    int getPoints() {
        return points;
    }

    void move(int x, int y) {
        this.x = x;
        this.y = y;
    }

    void remember(Point t) {
        if (t == null) {
            tile = t;
        } else {
            tile = new Point(t.x, t.y);
        }
    }

    Point recall() {
        return tile;
    }

    static void resize(int w0, int h0) {
        w = w0;
        h = h0;
    }

    boolean hit(int xp, int yp) {
        return (xp >= x && xp < x + w && yp >= y && yp < y + h);
    }
    private int font_ascent;

    void validate(Graphics g) {
        FontMetrics fm;
        if (h != lasth) {
            font = new Font("SansSerif", Font.BOLD, (int) (h * .6));
            g.setFont(font);
            fm = g.getFontMetrics();
            font_ascent = fm.getAscent();

            y0 = (h - font_ascent) * 4 / 10 + font_ascent;

            smfont = new Font("SansSerif", Font.BOLD, (int) (h * .3));
            g.setFont(smfont);
            fm = g.getFontMetrics();
            ys0 = y0 + fm.getAscent() / 2;
            lasth = h;
        }
        if (!valid) {
            valid = true;
            g.setFont(font);
            fm = g.getFontMetrics();
            w0 = fm.stringWidth(symbol);
            g.setFont(smfont);
            fm = g.getFontMetrics();
            ws0 = fm.stringWidth("" + points);
            int slop = w - (w0 + gap + ws0);
            x0 = slop / 2;
            if (x0 < 1) {
                x0 = 1;
            }
            xs0 = x0 + w0 + gap;
            if (points > 9) {
                xs0--;
            }
        }
    }

    void paint(Graphics g, int i) {
        Color c[] = colors[i];
        validate(g);
        g.setColor(c[NORMAL]);
        g.fillRect(x, y, w, h);
        g.setColor(c[BRIGHT]);
        g.fillRect(x, y, w - 1, 1);
        g.fillRect(x, y + 1, 1, h - 2);
        g.setColor(Color.black);
        g.fillRect(x, y + h - 1, w, 1);
        g.fillRect(x + w - 1, y, 1, h - 1);
        g.setColor(c[DIM]);
        g.fillRect(x + 1, y + h - 2, w - 2, 1);
        g.fillRect(x + w - 2, y + 1, 1, h - 3);
        g.setColor(Color.black);
        if (points > 0) {
            g.setFont(font);
            g.drawString(symbol, x + x0, y + y0);
            g.setFont(smfont);
            g.drawString("" + points, x + xs0, y + ys0);
        }
    }
}
//  Scrabblet.java
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;

public class Scrabblet extends Applet implements ActionListener {

    private ServerConnection server;
    private String serverName;
    private Bag bag;
    private Board board;
    private boolean single = false;
    private boolean ourturn;
    private boolean seen_pass = false;
    private Letter theirs[] = new Letter[7];
    private String name;
    private String others_name;
    private Panel topPanel;
    private Label prompt;
    private TextField namefield;
    private Button done;
    private TextField chat;
    private List idList;
    private Button challenge;
    private Canvas ican;

    @Override
    public void init() {
        setLayout(new BorderLayout());
        serverName = getCodeBase().getHost();
        if (serverName.equals("")) {
            serverName = "localhost";
        }
        ican = new IntroCanvas();
    }

    @Override
    public void start() {
        try {
            showStatus("Connecting to " + serverName);
            server = new ServerConnection(this, serverName);
            server.start();
            showStatus("Connected: " + serverName);

            if (name == null) {
                prompt = new Label("Enter your name here:");
                namefield = new TextField(20);
                namefield.addActionListener(this);
                topPanel = new Panel();
                topPanel.setBackground(new Color(255, 255, 200));
                topPanel.add(prompt);
                topPanel.add(namefield);
                add("North", topPanel);
                add("Center", ican);
            } else {
                if (chat != null) {
                    remove(chat);
                    remove(board);
                    remove(done);
                }
                nameEntered(name);
            }
            validate();
        } catch (IOException | HeadlessException e) {
            single = true;
            start_Game((int) (0x7fffffff * Math.random()));
        }
    }

    @Override
    public void stop() {
        if (!single) {
            server.quit();
        }
    }

    void add(String id, String hostname, String name) {
        delete(id); // in case it is already there.
        idList.add("(" + id + ")  " + name + "@" + hostname);
        showStatus("Choose a player from the list");
    }

    void delete(String id) {
        for (int i = 0; i < idList.getItemCount(); i++) {
            String s = idList.getItem(i);
            s = s.substring(s.indexOf("(") + 1, s.indexOf(")"));
            if (s.equals(id)) {
                idList.remove(i);
                break;
            }
        }
        if (idList.getItemCount() == 0 && bag == null) {
            showStatus("Wait for other players to arrive.");
        }
    }

    private String getName(String id) {
        for (int i = 0; i < idList.getItemCount(); i++) {
            String s = idList.getItem(i);
            String id1 = s.substring(s.indexOf("(") + 1, s.indexOf(")"));
            if (id1.equals(id)) {
                return s.substring(s.indexOf(" ") + 3, s.indexOf("@"));
            }
        }
        return null;
    }
    // we've been challenged to a game by "id".

    void challenge(String id) {
        ourturn = false;
        int seed = (int) (0x7fffffff * Math.random());
        others_name = getName(id);   // who was it?
        showStatus("challenged by " + others_name);

        // put some confirmation here...
        server.accept(id, seed);
        server.delete();
        start_Game(seed);
    }
    // our challenge was accepted.

    void accept(String id, int seed) {
        ourturn = true;
        others_name = getName(id);
        server.delete();
        start_Game(seed);
    }

    void chat(String id, String s) {
        showStatus(others_name + ": " + s);
    }
    // the other guy moved, and placed 'letter' at (x, y).

    void move(String letter, int x, int y) {
        for (int i = 0; i < 7; i++) {
            if (theirs[i] != null && theirs[i].getSymbol().equals(letter)) {
                Letter already = board.getLetter(x, y);
                if (already != null) {
                    board.moveLetter(already, 15, 15); // on the tray.
                }
                board.moveLetter(theirs[i], x, y);
                board.commitLetter(theirs[i]);
                theirs[i] = bag.takeOut();
                if (theirs[i] == null) {
                    showStatus("No more letters");
                }
                break;
            }
        }
        board.repaint();
    }

    void turn(int score, String words) {
        showStatus(others_name + " played: " + words + " worth " + score);
        done.setEnabled(true);
        board.othersTurn(score);
    }

    void quit(String id) {
        showStatus(others_name + " just quit.");
        remove(chat);
        remove(board);
        remove(done);
        nameEntered(name);
    }

    private void nameEntered(String s) {
        if (s.equals("")) {
            return;
        }
        name = s;
        if (ican != null) {
            remove(ican);
        }
        if (idList != null) {
            remove(idList);
        }
        if (challenge != null) {
            remove(challenge);
        }
        idList = new List(10, false);
        add("Center", idList);
        challenge = new Button("Challenge");
        challenge.addActionListener(this);
        add("North", challenge);
        validate();
        server.setName(name);
        showStatus("Wait for other players to arrive.");
        if (topPanel != null) {
            remove(topPanel);
        }
    }

    private void wepick() {
        for (int i = 0; i < 7; i++) {
            Letter l = bag.takeOut();
            board.addLetter(l);
        }
    }

    private void theypick() {
        for (int i = 0; i < 7; i++) {
            Letter l = bag.takeOut();
            theirs[i] = l;
        }
    }

    private void start_Game(int seed) {
        if (single) {
            Frame popup = new Frame("Scrabblet");
            popup.setSize(400, 300);
            popup.add("Center", ican);
            popup.setResizable(false);
            popup.setVisible(true);
            board = new Board();
            showStatus("no server found, playing solo");
            ourturn = true;
        } else {
            remove(idList);
            remove(challenge);
            board = new Board(name, others_name);
            chat = new TextField();
            chat.addActionListener(this);
            add("North", chat);
            showStatus("playing against " + others_name);
        }

        add("Center", board);
        done = new Button("Done");
        done.addActionListener(this);
        add("South", done);
        validate();

        bag = new Bag(seed);
        if (ourturn) {
            wepick();
            if (!single) {
                theypick();
            }
        } else {
            done.setEnabled(false);
            theypick();
            wepick();
        }
        board.repaint();
    }

    private void challenge_them() {
        String s = idList.getSelectedItem();
        if (s == null) {
            showStatus("Choose a player from the list then press Challenge");
        } else {
            remove(challenge);
            remove(idList);
            String destid = s.substring(s.indexOf('(') + 1, s.indexOf(')'));
            showStatus("challenging: " + destid);
            server.challenge(destid);  // accept will get called if
            // they accept.
            validate();
        }
    }

    private void our_turn() {
        String word = board.findwords();
        if (word == null) {
            showStatus("Illegal letter positions");
        } else {
            if ("".equals(word)) {
                if (single) {
                    return;
                }
                if (seen_pass) {
                    done.setEnabled(false);
                    server.turn("pass", 0);
                    showStatus("You passed");
                    seen_pass = false;
                } else {
                    showStatus("Press done again to pass");
                    seen_pass = true;
                    return;
                }
            } else {
                seen_pass = false;
            }
            showStatus(word);
            board.commit(server);
            for (int i = 0; i < 7; i++) {
                if (board.getTray(i) == null) {
                    Letter l = bag.takeOut();
                    if (l == null) {
                        showStatus("No more letters");
                    } else {
                        board.addLetter(l);
                    }
                }
            }
            if (!single) {
                done.setEnabled(false);
                server.turn(word, board.getTurnScore());
            }
            board.repaint();
        }
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();
        if (source == chat) {
            server.chat(chat.getText());
            chat.setText("");
        } else if (source == challenge) {
            challenge_them();
        } else if (source == done) {
            our_turn();
        } else if (source == namefield) {
            TextComponent tc = (TextComponent) source;
            nameEntered(tc.getText());
        }
    }
}
//  Server.java
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
import java.io.*;
import java.net.*;
import java.util.*;

public class Server implements Runnable {

    private int port = 6564;
    private Hashtable idcon = new Hashtable();
    private int id = 0;
    static final String CRLF = "\r\n";

    synchronized void addConnection(Socket s) {
        ClientConnection con = new ClientConnection(this, s, id);
        // we will wait for the ClientConnection to do a clean
        // handshake setting up its "name" before calling
        // set() below, which makes this connection "live."
        id++;
    }

    synchronized void set(String the_id, ClientConnection con) {
        idcon.remove(the_id);  // make sure we're not in there twice.
        con.setBusy(false);
        // tell this one about the other clients.
        Enumeration e = idcon.keys();
        while (e.hasMoreElements()) {
            String id = (String) e.nextElement();
            ClientConnection other = (ClientConnection) idcon.get(id);
            if (!other.isBusy()) {
                con.write("add " + other + CRLF);
            }
        }
        idcon.put(the_id, con);
        broadcast(the_id, "add " + con);
    }

    synchronized void sendto(String dest, String body) {
        ClientConnection con = (ClientConnection) idcon.get(dest);
        if (con != null) {
            con.write(body + CRLF);
        }
    }

    synchronized void broadcast(String exclude, String body) {
        Enumeration e = idcon.keys();
        while (e.hasMoreElements()) {
            String id = (String) e.nextElement();
            if (!exclude.equals(id)) {
                ClientConnection con = (ClientConnection) idcon.get(id);
                con.write(body + CRLF);
            }
        }
    }

    synchronized void delete(String the_id) {
        broadcast(the_id, "delete " + the_id);
    }

    synchronized void kill(ClientConnection c) {
        if (idcon.remove(c.getId()) == c) {
            delete(c.getId());
        }
    }

    public void run() {
        try {
            ServerSocket acceptSocket = new ServerSocket(port);
            System.out.println("Server listening on port " + port);
            while (true) {
                Socket s = acceptSocket.accept();
                addConnection(s);
            }
        } catch (IOException e) {
            System.out.println("accept loop IOException: " + e);
        }
    }

    public static void main(String args[]) {
        new Thread(new Server()).start();
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
        }
    }
}
//  ServerConnection.java
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
import java.io.*;
import java.net.*;
import java.util.*;

class ServerConnection implements Runnable {

    private static final int port = 6564;
    private static final String CRLF = "\r\n";
    private BufferedReader in;
    private PrintWriter out;
    private String id, toid = null;
    private Scrabblet scrabblet;

    public ServerConnection(Scrabblet sc, String site) throws IOException {
        scrabblet = sc;
        Socket server = new Socket(site, port);
        in = new BufferedReader(new InputStreamReader(server.getInputStream()));
        out = new PrintWriter(server.getOutputStream(), true);
    }

    private String readline() {
        try {
            return in.readLine();
        } catch (IOException e) {
            return null;
        }
    }

    void setName(String s) {
        out.println("name " + s);
    }

    void delete() {
        out.println("delete " + id);
    }

    void setTo(String to) {
        toid = to;
    }

    void send(String s) {
        if (toid != null) {
            out.println("to " + toid + " " + s);
        }
    }

    void challenge(String destid) {
        setTo(destid);
        send("challenge " + id);
    }

    void accept(String destid, int seed) {
        setTo(destid);
        send("accept " + id + " " + seed);
    }

    void chat(String s) {
        send("chat " + id + " " + s);
    }

    void move(String letter, int x, int y) {
        send("move " + letter + " " + x + " " + y);
    }

    void turn(String words, int score) {
        send("turn " + score + " " + words);
    }

    void quit() {
        send("quit " + id);  // tell other player
        out.println("quit"); // unhook
    }

    // reading from server...
    private Thread t;

    void start() {
        t = new Thread(this);
        t.start();
    }

    private static final int ID = 1;
    private static final int ADD = 2;
    private static final int DELETE = 3;
    private static final int MOVE = 4;
    private static final int CHAT = 5;
    private static final int QUIT = 6;
    private static final int TURN = 7;
    private static final int ACCEPT = 8;
    private static final int CHALLENGE = 9;
    private static Hashtable keys = new Hashtable();
    private static String keystrings[] = {
        "", "id", "add", "delete", "move", "chat",
        "quit", "turn", "accept", "challenge"
    };

    static {
        for (int i = 0; i < keystrings.length; i++) {
            keys.put(keystrings[i], new Integer(i));
        }
    }

    private int lookup(String s) {
        Integer i = (Integer) keys.get(s);
        return i == null ? -1 : i.intValue();
    }

    public void run() {
        String s;
        StringTokenizer st;
        while ((s = readline()) != null) {
            st = new StringTokenizer(s);
            String keyword = st.nextToken();
            switch (lookup(keyword)) {
                default:
                    System.out.println("bogus keyword: " + keyword + "\r");
                    break;
                case ID:
                    id = st.nextToken();
                    break;
                case ADD: {
                    String id = st.nextToken();
                    String hostname = st.nextToken();
                    String name = st.nextToken(CRLF);
                    scrabblet.add(id, hostname, name);
                }
                break;
                case DELETE:
                    scrabblet.delete(st.nextToken());
                    break;
                case MOVE: {
                    String ch = st.nextToken();
                    int x = Integer.parseInt(st.nextToken());
                    int y = Integer.parseInt(st.nextToken());
                    scrabblet.move(ch, x, y);
                }
                break;
                case CHAT: {
                    String from = st.nextToken();
                    scrabblet.chat(from, st.nextToken(CRLF));
                }
                break;
                case QUIT: {
                    String from = st.nextToken();
                    scrabblet.quit(from);
                }
                break;
                case TURN: {
                    int score = Integer.parseInt(st.nextToken());
                    scrabblet.turn(score, st.nextToken(CRLF));
                }
                break;
                case ACCEPT: {
                    String from = st.nextToken();
                    int seed = Integer.parseInt(st.nextToken());
                    scrabblet.accept(from, seed);
                }
                break;
                case CHALLENGE: {
                    String from = st.nextToken();
                    scrabblet.challenge(from);
                }
                break;
            }
        }
    }
}
```

*s22.postimg.org/mlftc25nh/Scrabblet.png

This wraps up Expert Java Programming Part 4 tutorial!!!


----------

