Search code examples
javaclient-serverrmi

Java RMI game not running


I've been banging my head against the wall trying to fix this. Pretty sure I lost some hair today.

Basically, we were given a few working classes and were asked to implement RMI on it. There were some errors in the given code to begin with, but I fixed them. Now I get no errors at all, and it isn't working.

Here are the classes:

GameClient.java - done by me

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class GameClient extends UnicastRemoteObject implements IGUI, IGameEngine, Runnable {
    IGameServer ser;
    PlayerInfo player;
    IGUI g;
    IGameEngine ge;

    public GameClient(IGameServer ss) throws RemoteException {
        ser = ss;
        ser.registerClient(this);
    }

    @Override
    public void markedAsOriginalDisplayMode() throws RemoteException {
        g.markedAsOriginalDisplayMode();
    }

    @Override
    public void noMarkedDisplayMode() throws RemoteException {
        g.noMarkedDisplayMode();
    }

    @Override
    public void normalDisplayMode() throws RemoteException {
        g.normalDisplayMode();
    }

    @Override
    public void update() throws RemoteException {
        g.update();
    }

    @Override
    public void exit(int i) throws RemoteException {
        g = null;
        setGUI(g);
        ser.setGameOver();
        System.exit(i);
    }

    @Override
    public PlayingBlock getNextBlock() throws RemoteException {
        return ser.getNextBlock();
    }

    @Override
    public PlayArea getPlayArea() throws RemoteException {
        return ser.getPlayArea();
    }

    @Override
    public String getPlayerName() throws RemoteException {
        return ser.getPlayerName();
    }

    @Override
    public int getPlayerScore() throws RemoteException {
        return ser.getPlayerScore();
    }

    @Override
    public boolean isGameOver() throws RemoteException {
        return ser.isGameOver();
    }

    @Override
    public void setGameOver() throws RemoteException {
        ser.setGameOver();
    }

    public void setGUI(IGUI gui) {
        g = gui;
    }

    @Override
    public void setPlayer(PlayerInfo pi) {
        try {
            ser.setPlayer(pi);
        }
        catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void setPlayerName(String name) throws RemoteException {
        ser.setPlayerName(name);
    }

    @Override
    public void start() throws RemoteException {
        ser.start();
    }

    @Override
    public void treatEvent(int event) throws RemoteException {
        ser.treatEvent(event);
    }

    @Override
    public void run() {
        String playerName = "Vasilis";
        setPlayer(new PlayerInfo(playerName));
        IGUI gui = new GUI(this, 20, 25);
        setGUI(gui);
    }
} 

IGameEngine.java

import java.rmi.RemoteException;

/**
 * Interface representing the logic of the game
 */

public interface IGameEngine {

    public void treatEvent(int event) throws RemoteException;

    public void start() throws RemoteException;

    public PlayingBlock getNextBlock() throws RemoteException;

    public boolean isGameOver() throws RemoteException;

    public void setGameOver() throws RemoteException;

    public PlayArea getPlayArea() throws RemoteException;

    public int getPlayerScore() throws RemoteException;

    public void setPlayerName(String name) throws RemoteException;

    public void setPlayer(PlayerInfo pi);

    public String getPlayerName() throws RemoteException;

    public void setGUI(IGUI gui);

    public void exit(int status) throws RemoteException;

}

GameEngine.java

import java.rmi.RemoteException;
import java.util.*;

class GameEngine {
    private PlayArea board;

    private PlayerInfo player;

    private PlayingBlock activeBlock;

    private PlayingBlock nextBlock;

    private Timer timer;

    private TimerTask pendingTask;

    private int timerValue;

    private float timerScaleFactor;

    private int minTimerValue;

    private int timerValueChangeInterval;

    private int numBlocks;

    private Random rng;

    private IGUI gui;

    private boolean gameOver;

    private boolean controlsLocked;

    public GameEngine(int size) {
        board = new PlayArea(size);
        rng = new Random(System.currentTimeMillis());
        resetGame();
    }

    protected void resetGame() {
        nextBlock = null;
        board.init();
        timerScaleFactor = Settings.TIMERSCALE;
        minTimerValue = Settings.MINTIMER;
        timerValue = Settings.INITTIMER;
        timerValueChangeInterval = Settings.CHANGETIMER;
        timer = new Timer();
        gameOver = false;
        controlsLocked = true;
        if (player != null) player.reset();
    }

    public void setGUI(IGUI gui) {
        this.gui = gui;
    }

    private void updateGUI() throws RemoteException {
        gui.update();
    }

    public void setPlayer(PlayerInfo player) {
        this.player = player;
    }

    public String getPlayerName() {
        return player.getName();
    }

    public void setPlayerName(String name) {
        player.setName(name);
    }

    public int getPlayerScore() {
        return player.getScore();
    }

    public PlayArea getPlayArea() {
        return board;
    }

    public boolean isGameOver() {
        return gameOver;
    }

    public void setGameOver() {
        this.gameOver = true;
    }

    public PlayingBlock getNextBlock() {
        return nextBlock;
    }

    // --> game_engine_timer_methods
    private void adjustTimerValue() {
        if (++numBlocks % timerValueChangeInterval == 0) {
            float nextTimerValue = (float) timerValue / timerScaleFactor;
            timerValue = nextTimerValue < minTimerValue ? minTimerValue : Math.round(nextTimerValue);
        }
    }

    private void rescheduleTimer() {
        stopTimer();
        final PlayingBlock block = activeBlock;
        pendingTask = new TimerTask() {
            public void run() {
                synchronized (GameEngine.this) {
                    if (block.equals(activeBlock)) try {
                        treatEvent(GameEvent.TIMEOUT);
                    }
                    catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        timer.schedule(pendingTask, timerValue);
    }

    // --<
    // --> game_engine_start_method
    public void start() throws RemoteException {
        resetGame();
        nextBlock = new PlayingBlock(rng.nextInt(3) + 1, Colour.randomColour(rng), PlayArea.randomSide(rng));
        nextBlock();
        updateGUI();
        controlsLocked = false;
        rescheduleTimer();
    }

    public void stopTimer() {
        if (pendingTask != null) pendingTask.cancel();
    }

    public void exit(int status) {
        System.exit(status);
    }

    // --<
    // --> game_engine_nextBlock_method
    private boolean nextBlock() {
        int size = board.getSize();
        switch (nextBlock.getSide()) {
            case PlayArea.NORTHSIDE:
                nextBlock.activate(new Coord(size / 2, 1).add(nextBlock.getCoG().neg()));
                break;
            case PlayArea.SOUTHSIDE:
                nextBlock.activate(new Coord(size / 2, size - 2).add(nextBlock.getCoG().neg()));
                break;
            case PlayArea.EASTSIDE:
                nextBlock.activate(new Coord(size - 2, size / 2).add(nextBlock.getCoG().neg()));
                break;
            case PlayArea.WESTSIDE:
                nextBlock.activate(new Coord(1, size / 2).add(nextBlock.getCoG().neg()));
                break;
            default: // should never occur
                break;
        }
        activeBlock = nextBlock;
        if (board.collision(activeBlock)) return false;
        else {
            board.flipDraw(activeBlock);
            nextBlock = new PlayingBlock(rng.nextInt(3) + 1, Colour.randomColour(rng), PlayArea.randomSide(rng));
            adjustTimerValue();
            return true;
        }
    }

    // --<
    // --> game_engine_movement_methods
    private boolean rotate() {
        return move(null);
    }

    private boolean slideActiveBlock() {
        return move(activeBlock.getSlidingDirection());
    }

    private boolean move(Direction direction) {
        boolean success = false;

        // is the move forbidden?
        if (direction != null && direction.equals(activeBlock.getSlidingDirection().getReverse())) return false;

        board.flipDraw(activeBlock);
        if (direction == null) activeBlock.rotate();
        else activeBlock.move(direction);
        if (!board.collision(activeBlock)) success = true;
        else // put activeBlock back
            if (direction == null) activeBlock.rotate();
            else activeBlock.move(direction.getReverse());
        board.flipDraw(activeBlock);
        return success;
    }

    // --<
    // --> game_engine_event_handler
    synchronized public void treatEvent(int event) throws RemoteException {
        if (controlsLocked || gameOver) return;

        switch (event) {
            case GameEvent.TIMEOUT:
                if (slideActiveBlock()) {
                    rescheduleTimer();
                    updateGUI();
                    break;
                }
                else {
                    updateScore();
                    return;
                }
            case GameEvent.UP:
                if (move(Direction.up())) updateGUI();
                break;
            case GameEvent.DOWN:
                if (move(Direction.down())) updateGUI();
                break;
            case GameEvent.RIGHT:
                if (move(Direction.right())) updateGUI();
                break;
            case GameEvent.LEFT:
                if (move(Direction.left())) updateGUI();
                break;
            case GameEvent.ROTATE:
                if (rotate()) updateGUI();
                break;
            case GameEvent.FLY:
                pendingTask.cancel();
                pendingTask = null;
                while (slideActiveBlock()) ;
                updateScore();
                return;
            default:
                // should never occur
        }
    }

    // --<
    // --> game_engine_scoring_methods
    private void updateScore() {
        controlsLocked = true;
        new Thread() {
            public void run() {
                int rounds = 0;
                while (findSquares(++rounds)) {
                    // animate square
                    for (int i = 0; i < 4; i++) {
                        if (i % 2 == 0) try {
                            gui.markedAsOriginalDisplayMode();
                        }
                        catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        else try {
                            gui.normalDisplayMode();
                        }
                        catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        try {
                            updateGUI();
                        }
                        catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        try {
                            Thread.sleep(150);
                        }
                        catch (InterruptedException e) {
                            e.printStackTrace();
                            exit(1);
                        }
                    }
                    try {
                        updateBoard();
                    }
                    catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }

                if (nextBlock()) {
                    rescheduleTimer();
                }
                else {
                    timer.cancel();
                    gameOver = true;
                }
                try {
                    updateGUI(); // new block is active, display
                }
                catch (RemoteException e) {
                    e.printStackTrace();
                }
                controlsLocked = false; // unlock controls
            }
        }.start();
    }

    private boolean findSquares(int round) {
        int roundScore = 0;
        // brute force
        boolean found = false;
        int playSize = board.getSize();
        for (int size = 3; size >= 2; size--)
            for (int x = 0; x < playSize; x++)
                for (int y = 0; y < playSize; y++) {
                    int squareColour = board.getSquareColour(x, y, size);
                    if (Colour.isPlayBrick(squareColour)) {
                        roundScore += size * size;
                        // player.incScore((int) Math.pow(size * size, round));
                        if (size == 2) {
                            board.mark(x, y, size);
                            found = true;
                        }
                    }
                }
        if (roundScore > 0) player.incScore((int) Math.pow(roundScore, round));
        return found;
    }

    public void updateBoard() throws RemoteException {
        Direction blockDir = activeBlock.getSlidingDirection();
        Direction updateDir = blockDir.getReverse();
        Coord checkBrick;
        boolean falling;
        int size = board.getSize();

        gui.noMarkedDisplayMode();
        for (int i = 0; i < size; i++) {
            falling = false;

            if (blockDir.equals(Direction.up())) checkBrick = new Coord(i, 0);
            else if (blockDir.equals(Direction.down())) checkBrick = new Coord(i, size - 1);
            else if (blockDir.equals(Direction.left())) checkBrick = new Coord(0, i);
            else checkBrick = new Coord(size - 1, i);

            for (int j = 0; j < size; j++) {
                int brickColour = board.getBrick(checkBrick);
                if (Colour.isMarked(brickColour)) {
                    falling = true;
                    board.setBrick(checkBrick, Colour.BASE);
                }
                else if (falling) if (Colour.isPlayBrick(brickColour)) {
                    board.setBrick(checkBrick, Colour.BASE);
                    PlayingBlock tmpBlock = new PlayingBlock(1, brickColour, PlayArea.NORTHSIDE);
                    // we just need any temporary block
                    tmpBlock.activate(checkBrick.add(blockDir.getCoordIncrement()));
                    while (!board.collision(tmpBlock)) {
                        // animate the fall
                        board.flipDraw(tmpBlock);
                        updateGUI();
                        try {
                            Thread.sleep(50);
                        }
                        catch (InterruptedException e) {
                            e.printStackTrace();
                            exit(1);
                        }
                        board.flipDraw(tmpBlock);
                        tmpBlock.move(blockDir);
                    }
                    tmpBlock.move(updateDir);
                    board.flipDraw(tmpBlock);
                }
                else {
                    break; /* stop falling, finish with this loop */
                }
                checkBrick = checkBrick.add(updateDir.getCoordIncrement());
            }
        }
        gui.normalDisplayMode();
    }
    // --<
}

IGUI.java

/**
 * The server's remote reference to a client
 *
 *
 */

import java.rmi.*;

public interface IGUI extends Remote {

    public void update() throws RemoteException;

    public void normalDisplayMode() throws RemoteException;

    public void noMarkedDisplayMode() throws RemoteException;

    public void markedAsOriginalDisplayMode() throws RemoteException;

}

Squares.java

import java.rmi.RemoteException;

//--> squares_class
class Squares {

    public static void main(String[] args) throws RemoteException {
        GameClient gc = new GameClient();
        String playerName = "Quidam";

        if (args.length == 1) if (args[0].length() > 15) playerName = args[0].substring(0, 15);
        else playerName = args[0];
        gc.setPlayer(new PlayerInfo(playerName));

        IGUI gui = new GUI(gc, 20, 25);
        gc.setGUI(gui);

    }
}
// --<

IGameServer.java

/**
 * The interface which defines what operations our server can perform for clients
 *
 *
 */

import java.rmi.*;

public interface IGameServer extends Remote {

    public void registerClient(IGUI clientGUI) throws RemoteException;

    public void treatEvent(int event) throws RemoteException;

    public void start() throws RemoteException;

    public PlayingBlock getNextBlock() throws RemoteException;

    public boolean isGameOver() throws RemoteException;

    public void setGameOver() throws RemoteException;

    public PlayArea getPlayArea() throws RemoteException;

    public int getPlayerScore() throws RemoteException;

    public void setPlayerName(String name) throws RemoteException;

    public void setPlayer(PlayerInfo pi) throws RemoteException;

    public String getPlayerName() throws RemoteException;

}

And here is the file I created, GameServer.java

import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;

/**
 * Created with IntelliJ IDEA.
 * User: akay
 * Date: 25/11/2013
 * Time: 19:07
 * To change this template use File | Settings | File Templates.
 */
public class GameServer extends UnicastRemoteObject implements IGameServer, Serializable {
    LinkedList<IGUI> array;
    GameEngine ge = new GameEngine(25);

    public GameServer() throws RemoteException {
        array = new LinkedList();
    }

    @Override
    public PlayingBlock getNextBlock() throws RemoteException {
        return ge.getNextBlock();
    }

    @Override
    public PlayArea getPlayArea() throws RemoteException {
        return ge.getPlayArea();
    }

    @Override
    public String getPlayerName() {
        return ge.getPlayerName();
    }

    @Override
    public int getPlayerScore() {
        return ge.getPlayerScore();
    }

    @Override
    public boolean isGameOver() throws RemoteException {
        return ge.isGameOver();
    }

    @Override
    public void registerClient(IGUI clientGUI) throws RemoteException {
        array.add(clientGUI);
        ge.setGUI(clientGUI);
    }

    @Override
    public void setGameOver() throws RemoteException {
        ge.setGameOver();
    }

    @Override
    public void setPlayer(PlayerInfo pi) {
        ge.setPlayer(pi);
    }

    @Override
    public void setPlayerName(String name) {
        ge.setPlayerName(name);
    }

    @Override
    public void start() throws RemoteException {
        ge.start();
    }

    @Override
    public void treatEvent(int event) throws RemoteException {
        ge.treatEvent(event);
    }

    public static void main(String args[]) throws RemoteException {

        Registry registry;
        GameServer s;
        final int port = Registry.REGISTRY_PORT;

        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new java.rmi.RMISecurityManager());
        }

        try {
            registry = LocateRegistry.createRegistry(port);
        }
        catch (RemoteException e) {
            System.out.println("The registry couldn't be created.");
            e.printStackTrace();
        }

        try {
            s = new GameServer();
            java.rmi.Naming.rebind("Server", s);
        }
        catch (MalformedURLException e) {
            System.out.println("Bad server URI in registration.");
        }
        catch (RemoteException e) {
            System.out.println("Couldn't bind server.");
            e.printStackTrace();
        }

        try {
            System.in.read();
        }
        catch (IOException e) {
        }

        System.exit(0);
    }
}

These files were modified to fix errors and implement RMI. Now, the steps that should be taken are as follows:

  1. Create your GameClient class which implements our IGUI and IGameEngine interfaces. It will locate the server using the registry, and will accept local calls from the GUI, translating them to remote calls on the server and returning any results.
  2. Change the GUI class so that its reference to a GameEngine is of type IGameEngine instead
  3. The Squares class, which will start the client-side part of the game, will now need to create an instance of the GameClient and pass it to the GUI as its reference to an IGameEngine
  4. Create your GameServer class which implements our IGameServer interface, and also contains a main() method to start the server and game engine
  5. Change the GameEngine class so that its reference to a GUI is now of type IGUI; this will be its remote reference to a client. You will need to catch some exceptions as the GameEngine is now calling the GUI remotely.
  6. Compile what you have done, fixing any errors you come across, then try running your distributed game. You may see some runtime exceptions which you will also need to address.

These are the recommended steps. I have implemented all but one, which is the third.

Any help is appreciated.

If you want to download all files in a zip file, here it is: http://akay.me/gameCode.zip

Thank you for any pointers.

EDIT: I have edited my Squares class to get the server name from registry by including the following:

String host = null;

Registry registry = LocateRegistry.getRegistry(host);
IGameServer s = (IGameServer) registry.lookup("Server");
GameClient gc = new GameClient(s);

as was suggested.

now I'm getting the following error:

java.rmi.MarshalException: error marshalling arguments; nested exception is: 
    java.io.NotSerializableException: PlayerInfo
    at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:138)
    at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:178)
    at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:132)
    at com.sun.proxy.$Proxy0.setPlayer(Unknown Source)
    at GameClient.setPlayer(GameClient.java:80)
    at Squares.main(Squares.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.io.NotSerializableException: PlayerInfo
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1165)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
    at sun.rmi.server.UnicastRef.marshalValue(UnicastRef.java:274)
    at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:133)
    ... 10 more
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at GameEngine.getPlayerName(GameEngine.java:67)

Solution

  • You really need to learn reading error messages. They come with a precise location of the problem, and a description of the problem which is also pretty clear:

    constructor GameClient in class GameClient cannot be applied to given types; required: IGameServer found: no arguments reason: actual and formal argument lists differ in length

    So, what does that mean? It means that you're calling new GameClient(), without any argument, whereas the constructor of GameClient is declared as

    public GameClient(IGameServer ss) throws RemoteException {
    

    So, you have to pass an object of type IGameServer to the constructor, instead of not passing anything.