1

In the board game I am designing, each user has a grid with 100 cells. The user clicks a cell in the computer's grid, which makes a sound and changes to a different colour. The computer then automatically clicks a cell in the user's grid, which makes a sound and changes to a different colour.

The user's clicks are dealt with via the MouseListener (and the MouseClicked method). Currently, the last thing the method does is call the computerMove() method. This method executes and performs the computer move before giving changing the current player back to human. The game proceeds when the human makes another mouse click.

Ideally, I'd like to have a pause (perhaps a second) between each player move. However, due to the fact the computerMove method is being called inside the MouseClicked method, this is proving troublesome. By using Thread.sleep and even a TimerTask, the best I can do is to slow the human player's move down. However, as soon as the player makes a mouse click, the computer instantaneously responds.

Does anybody have any suggestions as to how I could implement a delay? Do I need to change how I am calling the methods?

ComputerMove method:

    public void computerMove() {
    if (currentTurn == computer) {
        int xPos = randomGenerator.nextInt(10);
        int yPos = randomGenerator.nextInt(10);
        LogicCell attackedCell = new LogicCell(xPos, yPos);
        playerGridLogic.addCellsThatHaveBeenAttacked(attackedCell);

        if (playerGridLogic.getCellsWithShips().contains(attackedCell)) {
            playerGrid.getCellArray()[xPos][yPos].setBackground(Color.ORANGE);
            hitSound();
        }
        else {
            playerGrid.getCellArray()[xPos][yPos].setBackground(Color.MAGENTA);
            missSound();
        }
        nextTurn();
    }
}

Corresponding MouseClick method:

        @Override
    public void mouseClicked(MouseEvent e) {
        if (allComputerShipsPlaced && currentTurn == human) {
            ViewCell currentCell = (ViewCell) e.getSource();
            xPos = currentCell.getXPos();
            yPos = currentCell.getYPos();
            LogicCell attackedCell = new LogicCell(xPos, yPos);
            computerGridLogic.addCellsThatHaveBeenAttacked(attackedCell);

            if (computerGridLogic.getCellsWithShips().contains(attackedCell)) {
                cellArray[xPos][yPos].setBackground(Color.ORANGE);
                hitSound();
            }
            else {
                cellArray[xPos][yPos].setBackground(Color.MAGENTA);
                missSound();
            }
            nextTurn();
            computerMove();
        }
    }

missSound method() (hitSound is very similar):

    public static void missSound() {
    try {
        Clip clip = AudioSystem.getClip();
        clip.open(AudioSystem.getAudioInputStream(new File("water-splash.wav")));
        clip.start();
    }
    catch (Exception exc)
    {
        exc.printStackTrace(System.out);
    }
}

Edit: I've tried changing the sound classes to this, but to no avail:

    public static void hitSound()
{
    try
    {
        Clip clip = AudioSystem.getClip();
        clip.open(AudioSystem.getAudioInputStream(new File("bomb-explosion.wav")));
        clip.start();

        LineListener listener = new LineListener() {
            public void update(LineEvent event) {
                if (event.getType() != Type.STOP) {
                    return;
                }
                try {
                    queue.take();
                } catch (InterruptedException e) {
                    //ignore this
                }
            }
        };
        clip.addLineListener(listener);
    }
    catch (Exception exc)
    {
        exc.printStackTrace(System.out);
    }
}
Andrew Martin
  • 5,619
  • 10
  • 54
  • 92
  • Wouldn't it make more sense to implement a video game model of input processing and graphical redrawing, e.g. every 1/60th of a second wake up, process input, update the game state/mdoels, redraw the screen, then wait until the next 1/60th of a second? – Patashu Apr 30 '13 at 22:47
  • Possibly - it's just I have no idea how to do all that! – Andrew Martin Apr 30 '13 at 22:53
  • In C# you'd use XNA, I'm not sure if there's an equivalent for Java, it's definitely worth googling for though because I'm 100% certain people have thought about this hard already :) – Patashu Apr 30 '13 at 22:57

3 Answers3

1

You should be able to add a listener and react to the end of the sound. See this question: how can I wait for a java sound clip to finish playing back?

This works for me:

public class Test {
    public static void main(String[] args) {
        final Object foo = new Object();
        Clip clip;
        try {
            clip = AudioSystem.getClip();
            clip.open(AudioSystem.getAudioInputStream(new File("go.wav")));
            clip.addLineListener(new LineListener() {
                public void update(LineEvent event) {
                    if(event.getType() == LineEvent.Type.STOP){
                        System.out.println("done playing");
                        synchronized(foo) {
                            foo.notify();
                        }

                    }
                }
            });
            clip.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

        synchronized(foo) {
            try {
                foo.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
Community
  • 1
  • 1
Sarien
  • 6,647
  • 6
  • 35
  • 55
  • I've tried implementing this (posted results in OP above) but I still don't get a proper delay. – Andrew Martin Apr 30 '13 at 22:34
  • I have added another example for synchronizing the main thread. This should make things simpler. In your attempt, why did you call queue.take() instead of nextturn()? – Sarien Apr 30 '13 at 22:58
  • Hey sorry for late response - konked out last night. I only called it as the other SO question used it - I didn't know what it meant and was hoping by using it I would learn. – Andrew Martin May 01 '13 at 20:46
  • Sorry for dummy questions, but I'm struggling to implement this. I've never used synchronized before. Where does the synchronized method at the bottom go? In the music sound method, or main method? In addition, I assume the "object" in my example is the grid I want to pause - how do I pass it to this method though? Sorry if these are obvious. I confuse easily! – Andrew Martin May 01 '13 at 20:52
  • I have posted my complete working example. You should be able to use that but it would be wise to read an introduction to thread synchronization. – Sarien May 01 '13 at 22:24
0

I would consider calling computerMove inside nextTurn, and do the waiting inside nextTurn.

durron597
  • 31,968
  • 17
  • 99
  • 158
  • Unfortunately if I do that, when I click a square the sound plays normally, then there is a slight delay, then the square changes colour and instantly the computer square changes colour and his sound plays. Delay is in the wrong place. I need it to come after the square I've clicked has changed colour – Andrew Martin Apr 30 '13 at 22:19
0

You could use Thread.sleep in computerMove() to pause the execution. You can use clip.getMicrosecondsLength to get the length of the sound effect in microseconds. Multiply it with 1000 and pass it to sleep to wait for the sound effect to finish.

Clip clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(new File("water-splash.wav")));
clip.start();
int lengthInMilliseconds = clip.getMicrosecondLength() * 1000;
Thread.sleep(lengthInMilliseconds);
Philipp
  • 67,764
  • 9
  • 118
  • 153