import java.io.*;
import java.net.*;
import java.awt.*;
import java.util.*;

// interface and display classes
class Display {
  // contains the top-level frame and the menu handler
  // it is similar to the view in a document/view structure
  DisplayFrame frame;  // the top-level frame
  Game game;  // a back reference to the game (the document)
  // Image carrier;

  Display(Game g) {
    game = g;
    frame = new DisplayFrame("Battleship", this);
    frame.resize(750, 400);
    frame.pack();
    frame.setResizable(false);
  }
  
  void show() {
    frame.show();
  }

  GameBoard getGameBoard() {
    return frame.board1;
  }

  ScoreBoard getScoreBoard() {
    return frame.board2;
  }
}

class DisplayFrame extends Frame {
  // the main frame contains the two playing boards
  GameBoard board1;
  ScoreBoard board2;
  Display disp;  // a reference to the display object
  MenuItem m1, m2, m3, m4;
  Location square = null;  // set by HVDialog
  int nShip; // set by HVDialog
  Button h; // set by HVDialog
  Button v; // set by HVDialog
  HVDialog hvDialog = null; // set by HVDialog

  DisplayFrame(String title, Display d) {
    super(title);
    disp = d;
    // create the two boards and disable them
    board1 = new GameBoard(this);
    board1.disable();
    board2 = new ScoreBoard(this);
    board2.disable();
    setLayout(new GridLayout(1, 2));
    add(board1);
    add(board2);
    // set menu
    MenuBar mb = new MenuBar();
    setMenuBar(mb);
    Menu menu1 = new Menu("Game", true);
    mb.add(menu1);
    Menu menu2 = new Menu("Help", true);
    mb.add(menu2);
    m1 = new MenuItem("Play against computer");
    menu1.add(m1);
    m2 = new MenuItem("Play another program");
    menu1.add(m2);
    menu1.addSeparator();
    m3 = new MenuItem("Quit");
    menu1.add(m3);
    m4 = new MenuItem("About");
    menu2.add(m4);
    // place the window
    move(100, 200);
  }

  void computerPlay() {
    System.out.println("Resetting for computer play"); // TRACE
    disp.game.computerPlay();
  }

  void netPlay() {
    System.out.println("Resetting for net play"); // TRACE
    disp.game.netPlay();
  }

  public void repaint() {
    board1.repaint();
    board2.repaint();
  }
  
  public boolean handleEvent(Event e) {
    if (e.id == Event.WINDOW_DESTROY) {
      QuitDialog d = new QuitDialog(this);
      d.show();
      return true;
    }
    return super.handleEvent(e);
  }

  public boolean action(Event e, Object arg) {
    if (e.target == m1) {
      disp.game.reset();
      computerPlay();
    }
    else if (e.target == m2) {
      disp.game.reset();
      netPlay();
    }
    else if (e.target == m3) {
      QuitDialog d = new QuitDialog(this);
      d.show();
    }
    else if (e.target == m4) {
      MessageBox d = new MessageBox(this,
				    "Battleship V" +
				    Game.version + " (Roger Hartley)",
				    "Battleship");
      d.show();
    }
    return true;
  }
}

class DisplayBoard extends Canvas {
  // a playing board is a grid painted on a canvas
  static final int HSIZE = 300;
  static final int VSIZE = 300;
  static final int NSQUARES = 10;
  int hSpacing;
  int vSpacing;
  DisplayFrame parent;

  DisplayBoard(DisplayFrame frame) {
    resize(HSIZE, VSIZE);
    parent = frame;
  }

  Game getGame() {
    return parent.disp.game;
  }

  public Dimension getPreferredSize() {
    return getMinimumSize();
  }
  
  public Dimension getMinimumSize() {
    return new Dimension(HSIZE, VSIZE);
  }

  Location getSquare(int x, int y) {
    return new Location(x / (size().width / NSQUARES),
         y / (size().height / NSQUARES));
  }

  void paintGrid(Graphics g) {
    Rectangle r = bounds();
    // set the canvas size to be a multiple of the square size
    resize(r.width - r.width % NSQUARES + 1,
	    r.height - r.height % NSQUARES + 1);
    // draw the grid
    hSpacing = r.width / NSQUARES;
    vSpacing = r.height / NSQUARES;
   // System.out.println("H spacing: " + hSpacing +
   //		       " V spacing: " + vSpacing); // TRACE
    for (int x = 0; x <= r.width; x += hSpacing)
      g.drawLine(x, 0, x, r.height);
    for (int y = 0; y <= r.height; y += vSpacing)
      g.drawLine(0, y, r.width, y);
  }
}

class GameBoard extends DisplayBoard {
  Player player;
  DisplayFrame frame;

  GameBoard(DisplayFrame frame) {
    super(frame);
    this.frame = frame;
  }

  public void paint(Graphics g) {
    //System.out.println("painting"); // TRACE
    setBackground(Color.blue);
    //System.out.println("Game"); // TRACE
    //      g.drawImage(parent.disp.carrier, 0, 0, 30, 20, this);
    // draw the grid
    paintGrid(g);
    // display the fleet
    Fleet fleet = getGame().getPlayerFleet();
    for (int n = 0; n < Game.SHIPDATA.length; n++) {
      Ship s = fleet.getShip(n);
      g.setColor(Game.SHIPDATA[n].color);
      if (s != null)
	for (int loc = 0; loc < s.locations.length; loc++) {
	  int x = s.locations[loc].col * hSpacing;
	  int y = s.locations[loc].row * vSpacing;
	  int rectWidth = hSpacing * 3 / 5;
	  int rectHeight = vSpacing * 3 / 5;
	  int rectXOffset = hSpacing / 5;
	  int rectYOffset = vSpacing / 5;
	  g.fillRect(x + rectXOffset, y + rectYOffset,
		     rectWidth, rectHeight);
	}
    }
    // now display the hits and misses
    player = frame.disp.game.one;
    Field field = player.gameField;
    int hitOrMiss = Square.UNTRIED;
    String c = null;
    Color color = Color.black;
    for (int row = 0; row < DisplayBoard.NSQUARES; row++)
      for (int col = 0; col < DisplayBoard.NSQUARES; col++) {
        hitOrMiss = field.getHitOrMiss(row, col);
        if (hitOrMiss != Square.UNTRIED) {
          switch (hitOrMiss) {
            case Square.HIT: c = "H";  break;
            case Square.MISS: c = "M"; break;
            case Game.CARRIER: c = "A"; break;
            case Game.BATTLESHIP: c = "B"; break;
            case Game.CRUISER: c = "C"; break;
            case Game.DESTROYER: c = "D"; break;
            case Game.SUBMARINE: c = "S"; break;
        }
        g.setColor(color);
        g.drawString(c, col * hSpacing + hSpacing / 2 - 5,
                    row * vSpacing + vSpacing / 2 + 5);
      }
    }
  }

  // mouse handling to get the ship's location
  public boolean mouseDown(Event e, int x, int y) {
    if (parent.hvDialog == null)
      return false;
    System.out.println("Square clicked " + x + "," + y); // TRACE
    // this calculates a grid position from the mouse coordinates
    parent.square = getSquare(x, y);
    parent.hvDialog.setTitle("Place your " +
			     Game.SHIPDATA[parent.nShip].name + " starting at " +
	     parent.square.getGrid());
    parent.h.enable(true);
    parent.v.enable(true);
    return true;
  }		    
}

class ScoreBoard extends DisplayBoard {
  DisplayFrame frame;
  HumanPlayer player;
  Player opponent;

  ScoreBoard(DisplayFrame frame) {
    super(frame);
    this.frame = frame;
  }

  public void paint(Graphics g) {
    //System.out.println("painting"); // TRACE
    setBackground(Color.red);
    //System.out.println("Score"); // TRACE
    // draw the grid
    paintGrid(g);
    // display the hits and misses
    player = (HumanPlayer)frame.disp.game.one;
    Field field = player.scoreField;
    int hitOrMiss = Square.UNTRIED;
    String c = null;
    for (int row = 0; row < DisplayBoard.NSQUARES; row++)
      for (int col = 0; col < DisplayBoard.NSQUARES; col++) {
	hitOrMiss = field.getHitOrMiss(row, col);
	Color color = Color.black;
	if (hitOrMiss != Square.UNTRIED) {
	  switch (hitOrMiss) {
	  case Square.HIT: c = "H";  break;
	  case Square.MISS: c = "M"; break;
	  case Game.CARRIER: c = "A";
	    color = Game.SHIPDATA[0].color;
	    break;
	  case Game.BATTLESHIP: c = "B";
	    color = Game.SHIPDATA[1].color;
	    break;
	  case Game.CRUISER: c = "C";
	    color = Game.SHIPDATA[2].color;
	    break;
	  case Game.DESTROYER: c = "D";
	    color = Game.SHIPDATA[3].color;
	    break;
	  case Game.SUBMARINE: c = "S";
	    color = Game.SHIPDATA[4].color;
	    break;
	  default: c = "X";
	  }
	  g.setColor(color);
	  g.drawString(c, col * hSpacing + hSpacing / 2 - 5,
		       row * vSpacing + vSpacing / 2 + 5);
	}
      }
  }

  public boolean mouseDown(Event e, int x, int y) {
    // check if guess made already
    Location l = getSquare(x, y);
    int horm = player.scoreField.getHitOrMiss(l.row, l.col);
    System.out.println("HORM = " + horm);
    if (horm != Square.UNTRIED) {
      new MessageBox(frame, "You already made a guess at " +
		     l.getGrid(),
		     player.getName()+ "...").show();
      return true;
    }

    // process this guess for hit or miss on the 
    // opponent's fleet
    disable(); // while we get the opponent's guess
    System.out.println("Score board disabled in mouse handler");
    player = (HumanPlayer)frame.disp.game.one;
    opponent = frame.disp.game.two;
    Ship hitShip;
    try {
      hitShip = opponent.processGuess(l);
    }
    catch (QuitException ex) {
      System.out.println("Quit: " + ex.getMessage());
      if (!ex.getMessage().equals("new")) {
	Trace.p("Quitting from mouseDown");
	frame.disp.game.os.println("quit");
	frame.disp.game.os.flush();
	frame.dispose();
	System.exit(0);
      }
      return true; // leave board disabled - only menu is possible
    }
      
    if (player.scoreField.record(l, hitShip))
      	new MessageBox(frame, "You sank the " + hitShip.name + 
		       " at " + l.getGrid(),
		       player.getName()+ "...").show();
    frame.repaint();
    if (opponent.fleet.isSunk(player.scoreField)) {
      new MessageBox(frame, "You have won",
		     "THE WINNER!!").show();
      return true;
    }
    l = opponent.getGuess();
    try {
      hitShip = player.processGuess(l);
    }
    catch (QuitException ee) {}
    player.gameField.record(l, hitShip);
    if (opponent.scoreField.record(l, hitShip))// so that the computer can 
                                               // make sensible guesses
      	new MessageBox(frame, "Your opponent sank the " + hitShip.name + 
		       " at " + l.getGrid(),
		       opponent.getName()+ "...").show();
    if (player.fleet.isSunk(player.gameField)) {
      new MessageBox(frame, "The other guy has won",
		     "THE WINNER!!").show();
    } // leave board disabled
    else {
      enable();
      System.out.println("Score board enabled");
    }
    return true;
  }
}

class MessageBox extends Dialog {
  // a general class for a modal message box that must be
  // dismissed be pressing the OK button
  MessageBox(Frame f, String msg, String title) {
    super(f, true);
    resize(500, 500);
    setTitle(title);
    setLayout(new FlowLayout());
    add(new Label(msg));
    Button ok = new Button("OK");
    add (ok);
    pack();
    Point loc = f.location();
    if (loc.y >= 50)
      loc.translate(0, -50);
    move(loc.x, loc.y);
  }

  public boolean action(Event e, Object arg) {
    dispose();
    return true;
  }
}

class Splash extends Dialog {
  // a general class for a non-modal box that displays
  // a message - it must be dismissed by the program
  Splash(Frame f, String msg) {
    super(f, "Guess!", false);
    resize(500, 500);
    setLayout(new FlowLayout());
    add(new Label(" "));
    add(new Label(msg));
    pack();
    Point loc = f.location();
    if (loc.y >= 50)
      loc.translate(0, -50);
    move(loc.x, loc.y);
  }
}

class HVDialog extends Dialog {
  // gets the user's choice of horizontal or vertical for any ship
  // the sequencing (click, choose h or v) is done in Player
  DisplayFrame parent;
  Fleet fleet; // ref to the player's fleet
  Boolean horv;
  Game game;
  
  HVDialog(DisplayFrame f, String title, Game g) {
    super(f, title, false);  // non-modal
    resize(500, 500);
    setTitle("Click on a square to place your " + Game.SHIPDATA[0].name);
    parent = f;
    game = g;
    horv = null;
    fleet = g.one.fleet;
    parent.nShip = 0;
    setLayout(new FlowLayout());
    add(new Label("Place it horizontally or vertically?"));
    parent.h = new Button("Horizontal");
    parent.v = new Button("Vertical");
    add(parent.h);
    add(parent.v);
    parent.h.enable(false);
    parent.v.enable(false);
    pack();
    Point loc = f.location();
    loc.translate(350, 50);
    move(loc.x, loc.y);
    parent.hvDialog = this;
  }
  
  public boolean action(Event e, Object arg) {
    if (e.target == parent.h)
      horv = new Boolean(true);
    else
      horv = new Boolean(false);
    // make a ship and add it to the fleet
    try {
      fleet.addShip(parent.nShip, parent.square, horv.booleanValue());
      parent.repaint();
      ++parent.nShip;
    }
    catch (OffBoardException ex) {
      new MessageBox(parent,
		     "Your " + ex.getMessage() +
		     " would be off the board - try again",
		     "Oops!").show();
    }
    catch (OverlapException ex) {
      new MessageBox(parent,
		     "Your " + ex.getMessage() +
		     " would overlap another ship - try again",
		     "Oops!").show();
    }
    
    if (parent.nShip == 5) { // all ships have been placed 
      dispose();
      parent.hvDialog = null;
      new MessageBox(parent, "Ready to start?", "Battleship").show();
      // don't enable the score board until a connection is established
      parent.disp.getGameBoard().disable();
      System.out.println("Game board disabled during ship placement");
      if (game.two instanceof NetPlayer && !((HumanPlayer)(game.one)).connected)
        game.makeConnection();
      else {
	System.out.println("Score board enabled");
	parent.disp.getScoreBoard().enable();
      }
    }
    else {
      setTitle("Click on a square to place your " +
	       Game.SHIPDATA[parent.nShip].name);
      parent.h.enable(false);
      parent.v.enable(false);
      horv = null;
      parent.square = null;
    }
    return true;
  }
}

class NameDialog extends Dialog {
  TextField nameBox;
  DisplayFrame parent;
 
  NameDialog(DisplayFrame frame) {
    super(frame, "Battleship", true);
    parent = frame;
    resize(500, 500);
    setLayout(new FlowLayout());
    add(new Label("What is your name?"));
    nameBox = new TextField(32);
    nameBox.setEditable(true);
    add(nameBox);
    pack();
    Point loc = parent.location();
    loc.translate(150, 150);
    move(loc.x, loc.y);
  }

  String getPlayerName() {
    return nameBox.getText();
  }

  public boolean action(Event e, Object arg) {
    dispose();
    return true;
  }
}

class QuitDialog extends Dialog {
  // a dialog to confirm exiting from the game
  DisplayFrame parent;  // a back reference to the main frame
  Button yes, no;

  QuitDialog(DisplayFrame p) {
    super(p, "Exiting Battleship", true);
    resize(500, 500);
    parent = p;
    System.out.println("Quit dialog"); // TRACE
    setLayout(new FlowLayout());
    add(new Label("Are you sure?"));
    yes = new Button("Yes");
    add(yes);
    no = new Button("No");
    add(no);
    pack();
    Point loc = parent.location();
    loc.translate(150, 150);
    move(loc.x, loc.y);
  }

  public boolean action(Event e, Object arg) {
    if (e.target == yes) { // quitting time
      // if we are connected, send a quit to the server
      if (((HumanPlayer)(parent.disp.game.one)).connected) {
	  System.out.println("Sending quit");
	  parent.disp.game.os.println("quit");
	  parent.disp.game.os.flush();
	  parent.dispose();
	  System.exit(0);
      }
      else {
	parent.dispose();
	System.exit(0);
      }
    }
    else
      dispose();  // don't quit
    return true;
  }
}

class NameListDialog extends Dialog {
  String choice;
  DisplayFrame parent;
    
  NameListDialog(DisplayFrame frame, String names) {
    super(frame, true);
    parent = frame;
    resize(500, 500);
    setTitle("Battleship");
    List listBox = new List(5, false);
    int start = 0;
    int end = names.indexOf(" ");
    while (end != -1) {
      listBox.addItem(names.substring(start, end));
      start = end + 1;
      end = names.indexOf(" ", start);
    }
    listBox.addItem("none");
    setLayout(new FlowLayout());
    add(new Label("Choose a player"));
    add(listBox);
    pack();
    Point loc = parent.location();
    loc.translate(150, 150);
    move(loc.x, loc.y);
    
  }
  
  public boolean action(Event e, Object arg) {
    choice = (String)arg;
    dispose();
    return true;
  }
  
  String getResponse() {
    return choice;
  }
}

class ConnectDialog extends Dialog {
  boolean answer;
  Button yes;
  Button no;
  DisplayFrame parent;
  
  ConnectDialog(DisplayFrame frame, String name) {
    super(frame, true);
    parent = frame;
    resize(500, 500);
    setTitle("Battleship");
    yes = new Button("Yes");
    no = new Button("No");
    setLayout(new FlowLayout());
    add(new Label("Do you want to play " + name + "?"));
    add(yes);
    add(no);
    pack();
    Point loc = parent.location();
    loc.translate(150, 150);
    move(loc.x, loc.y);
  }
  
  public boolean action(Event e, Object arg) {
    if (e.target == yes)
      answer = true;
    else if (e.target == no)
      answer = false;
    dispose();
    return true;
  }
  
  String getAnswer() {
    if (answer)
      return "yes";
    else
      return "no";
  }
}

class PlayAgainDialog extends Dialog {
  boolean answer;
  Button yes;
  Button no;
  DisplayFrame parent;
  
  PlayAgainDialog(DisplayFrame frame) {
        super(frame, true);
	parent = frame;
        resize(500, 500);
        setTitle("Battleship");
        yes = new Button("Yes");
        no = new Button("No");
        setLayout(new FlowLayout());
        add(new Label("Opponent has quit. Do want to play again?"));
        add(yes);
        add(no);
        pack();
	Point loc = parent.location();
	loc.translate(150, 150);
	move(loc.x, loc.y);
    }
    
    public boolean action(Event e, Object arg) {
        if (e.target == yes)
            answer = true;
        else if (e.target == no)
            answer = false;
        dispose();
        return true;
    }
    
    boolean getAnswer() {
      return answer;
    }
}

// game classes

class ShipData {
  String name;
  int length;
  int id;
  Color color;

  ShipData(String n, int l, int i, Color c) {
    name = n;
    length = l;
    id = i;
    color = c;
  }
}

class Game {
  // the top-level class for the game data
  // this is like the document ina  document/view system
  static final double version = 6.4;
  static final int SHIPBASE = 1000;
  static final int CARRIER = SHIPBASE;
  static final int BATTLESHIP = SHIPBASE + 1;
  static final int CRUISER = SHIPBASE + 2;
  static final int DESTROYER = SHIPBASE + 3;
  static final int SUBMARINE = SHIPBASE + 4;
  static final ShipData[] SHIPDATA = {
    new ShipData("Aircraft Carrier", 5, CARRIER, Color.white),
    new ShipData("Battleship", 4, BATTLESHIP, Color.lightGray),
    new ShipData("Cruiser", 3, CRUISER, Color.green),
    new ShipData("Destroyer", 3, DESTROYER, Color.cyan),
    new ShipData("Submarine", 2, SUBMARINE, Color.yellow)};
  Player one = null;
  Player two = null;
  Display disp; // a reference to the display object (the view)
  // net connection stuff
  String serverHost;
  static final int port = 4444;
  Socket socket;
  DataInputStream is = null;
  PrintStream os = null;
  ConnectState cs = null;
  ConnectThread connectThread;
  String input = null, output = null;

  Game(String host) {
    disp = new Display(this);
    serverHost = host;
    one = new HumanPlayer(disp);
    two = new ComputerPlayer(disp); // default - overridden by netPlay
    disp.show();
  }

  void makeConnection() {
    connectThread = new ConnectThread(this);
    connectThread.start();
  }

  void reset() {
    one.reset();
    if (two != null)
      two.reset();
  }

  void netPlay() {
    // place ships before contacting the server
    one.placeShips();
    if (two instanceof ComputerPlayer) // need to make a NetPlayer
      two = new NetPlayer(disp);
  }
  
  void computerPlay() {
    // let the computer place first
    two.placeShips();
    // now wait until the human has placed all ships
    one.placeShips();
  }

  Fleet getPlayerFleet() {
      return one.fleet;
  }
      
  public static void main(String[] args) {
    Trace.p("Running...");
    if (args.length > 0)
      new Game(args[0]); // just create a game and wait for user choices
    else
      new Game("mesa.cs.nmsu.edu");
  }
}

class ConnectThread extends Thread {
  Game game;

  ConnectThread(Game g) {
    game = g;
  }

  public void run() {
    game.cs = new ConnectState(game.two.getName(), game.disp.frame);
    try {
      game.socket = new Socket(game.serverHost, game.port);
      game.is = new DataInputStream(game.socket.getInputStream());
      game.os = new PrintStream(game.socket.getOutputStream());
      // start by processing the startup messages
      game.disp.getScoreBoard().disable();
      System.out.println("Score board disabled in ConnectThread");
      boolean stillgo = true;
      while (stillgo) {
        game.input = game.is.readLine();
	if (game.input == null) {
	  ((HumanPlayer)(game.one)).connected = false;
	  return;  // something bad happend to the connection
	}
	else
	  ((HumanPlayer)(game.one)).connected = true;
	System.out.println("ConnectThread received: " + game.input);
        if (game.input.startsWith("guess")) {
	  if (game.input.equals("guess?")) {
	    new MessageBox(game.disp.frame,
			   "You go first", "Battleship").show();
	    game.output = "guess ";
	    // time to go to the mouse handler
	  }
	  else {
	    Location l = Location.getLoc(game.input);
	    // set the hit or miss to send after the first click
	    Ship hitShip = null;
	    try {
	      hitShip = game.one.processGuess(l);
	    }
	    catch (QuitException e) {
	      Trace.p("quitting");
	      game.os.println("quit");
	      game.os.flush();
	      System.exit(0);
	    }
	    game.one.gameField.record(l, hitShip);
	    if (hitShip == null)
	      game.output = "miss ";
	    else
	      game.output = "hit ";
	  }
	  game.disp.getScoreBoard().enable();    
	  System.out.println("Score board enabled " +
			     game.disp.getScoreBoard().isEnabled());
	  stillgo = false;
        }
        else {
	  try {
            game.output = game.cs.processInput(game.input);
	  }
	  catch (QuitException e) {
	    System.out.println("Caught quit");
	    game.os.println("quit");
	    game.os.flush();
	    System.exit(0);
	  }
	  System.out.println("Sent: " + game.output);
	  game.os.println(game.output);
	  game.os.flush();
        }
      }
    }
    catch (IOException ex) {
      new MessageBox(game.disp.frame,
		     "Error contacting server" + game.serverHost,
		     "Battleship").show();
      System.exit(1);
    }
  }
}

abstract class Player {
  // a player has a score board and a fleet
  Display disp;
  Field scoreField; // holds the hit or miss responses from guesses 
  Field gameField; // holds the hit or misses from the opponent's guesses
  Fleet fleet = new Fleet(); // make an empty fleet
  Splash guess = null;

  Player(Display disp) {
    scoreField = new Field(disp.frame, this);
    gameField = new Field(disp.frame, this);
    this.disp = disp;
  }

  abstract void placeShips();
  abstract String getName();
  abstract Location getGuess();

  void reset() {
    scoreField.clear();
    gameField.clear();
    fleet.clear();
  }

  Ship processGuess(Location sq) throws QuitException {
    System.out.println("Processing guess in square " +
		       "(" + sq.col + "," + sq.row +
		       ")");
    if (guess != null)
      guess.dispose();
    guess = new Splash(disp.frame, "Your opponent guessed " + sq.getGrid());
    guess.show();
    Ship ship = fleet.hit(sq);
    // the net player needs a string with hit/miss/sunk
    if (ship == null) 
        disp.game.output = "miss ";
    else
        disp.game.output = "hit ";
    // for recording, input has to to reflect the hit/miss/sunk
    disp.game.input = disp.game.output;
    //********************** test for sunk and change output to sunk <x>
    return ship;
  }
}

class HumanPlayer extends Player {
  GameBoard board;  // reference to the player's board
  HVDialog s;
  boolean  connected = false;
  
  HumanPlayer(Display disp) {
    super(disp);
    board = disp.getGameBoard();
  }

  String getName() {
    return "You";
  }

  Location getGuess() { return null; } // not used
  
  void guess(Player p) {
    Splash s = new Splash(board.parent,
			  "Make a guess by clicking in a square");
    s.show();
  }
  
  void placeShips() {
    // enable the game board,  the score board is still disabled
    board.enable();
    // initialize choosing of the carrier, and put up the non-modal
    // dialog to choose ship orientation
    //System.out.println("placing " + Game.SHIPDATA[0].name); // TRACE
    s = new HVDialog((DisplayFrame)board.getParent(),
		     Game.SHIPDATA[0].name, disp.game);
    s.show();
  }
  
}

class ComputerPlayer extends Player {
  // a class to override placeShips for random generation
  Random gen = new Random();
  
  ComputerPlayer(Display disp) {
    super(disp);
    this.disp = disp;
  }
  
  String getName() {
    return "Computer";
  }
  
  void placeShips() {
    // place the ships at random
    int nShip = 0;
    while (nShip < Game.SHIPDATA.length) {
      int x = Math.abs(gen.nextInt()) % DisplayBoard.NSQUARES;
      int y = Math.abs(gen.nextInt()) % DisplayBoard.NSQUARES;
      int n = Math.abs(gen.nextInt()) % 2;
      boolean horv = true;
      String hv = "H";
      if (n == 0) {
	horv = false;
	hv = "V";
      }
      Location loc = new Location(x, y);
      //System.out.println("Computer adding " + Game.SHIPDATA[nShip].name +
      //			 " at " + loc.getGrid() + " " + hv);
      try {
	fleet.addShip(nShip, loc, horv);
	++nShip;
      }
      catch (OverlapException e) {
	System.out.println("Overlaps");
      }
      catch (OffBoardException e) {
	System.out.println("Off board");
      }
    }
  }

  Location getGuess() {
    System.out.println("Getting guess from computer");
    
    int row, col;
    do {
      col = Math.abs(gen.nextInt()) % DisplayBoard.NSQUARES;
      row = Math.abs(gen.nextInt()) % DisplayBoard.NSQUARES;
    } while (scoreField.getHitOrMiss(row, col) != Square.UNTRIED);
    return new Location(col, row);
  }
}

class NetPlayer extends Player {
  String name = null;
  Display disp;
  Splash guess;

  NetPlayer(Display disp) {
    super(disp);
    this.disp = disp;
    NameDialog d = new NameDialog(disp.frame);
    d.show();
    name = d.getPlayerName();
    disp.frame.setTitle(disp.frame.getTitle() + ":" + name);
  }
  
  String getName() {
    return name;
  }

  Location getGuess() {
    System.out.println("Getting guess from net player");
    // game.input contains the guess
    return Location.getLoc(disp.game.input);
  }

  void placeShips() {}

  Ship processGuess(Location sq) throws QuitException {
    // overrides Player version
    // append the x, y to the current output (last hit/miss/sunk)
    if (guess != null)
      guess.dispose();
    guess = new Splash(disp.frame, "You guessed " + sq.getGrid());
    guess.show();
    String output = disp.game.output + sq.col + " " + sq.row;
    // send it to the opponent
    disp.game.os.println(output);
    disp.game.os.flush();
    System.out.println("Sent: " + output);
    // wait for a reply
    String input = null;
    try {
        input = disp.game.is.readLine();
    }
    catch (IOException e) {}
    System.out.println("NetPlayer Received: " + input);
    // process the input to give a chance for quitting
    try {
      disp.game.input = disp.game.cs.processInput(input); 
    }
    catch (IOException e) {
      Trace.p("IO error");
      disp.game.os.println("quit");
      disp.game.os.flush();
      System.exit(0);
    }
    // we don't know which ship it is, so just return null
    System.out.println("Processed to " + disp.game.input);
    if (disp.game.input.equals("new")) {
      throw new QuitException("new");
    }
    return null;
  }
}

class Field {
  // this class is unused so far
  Square[][] squares =
           new Square[DisplayBoard.NSQUARES][DisplayBoard.NSQUARES];
  DisplayFrame frame;
  Player owner;

  Field(DisplayFrame frame, Player player) {
    this.frame = frame;
    owner = player;
    for (int row = 0; row < DisplayBoard.NSQUARES; row++)
      for (int col = 0; col < DisplayBoard.NSQUARES; col++)
	squares[row][col] = new Square(row, col);
  }
  
  int getHitOrMiss(int r, int c) {
    int horm = squares[r][c].hitOrMiss;
    return horm;
  }
  
  boolean record(Location loc, Ship hitShip) {
    System.out.println("IN: " + frame.disp.game.input +
                        " OUT: " + frame.disp.game.output);
    if (hitShip != null) {
      System.out.println("Recording hit in " + loc.col + "," + loc.row);
      squares[loc.row][loc.col].setHit();
      if (hitShip.isSunk(this)) {
	for (int l = 0; l < hitShip.size; l++) {
	  Location loc1 = hitShip.locations[l];
	  squares[loc1.row][loc1.col].setSunk(hitShip.name);
	}
        return true;
      }
      else
        return false;
    }
    else {
      String input = frame.disp.game.input;
      if (input != null) {
        // the hit or miss was received from the opponent
        if (input.startsWith("hit") || input.startsWith("sunk")) {
          System.out.println("Recording hit in " + loc.col + "," + loc.row);
          squares[loc.row][loc.col].setHit();
          if (input.startsWith("sunk")) 
            // set ship code from sunk message **************************
            return true;
          else
            return false;
        }
        else {
          System.out.println("Recording miss in " + loc.col + "," + loc.row);
          squares[loc.row][loc.col].setMiss();
          return false;
        }
      }
      else {
        System.out.println("Recording miss in " + loc.col + "," + loc.row);
        squares[loc.row][loc.col].setMiss();
        return false;
      }
    }
  }
  
  void clear() {
    for (int row = 0; row < DisplayBoard.NSQUARES; row++)
      for (int col = 0; col < DisplayBoard.NSQUARES; col++)
	squares[row][col].setUntried();
  }    
}

class Ship {
  int size;
  String name;
  Location[] locations;
  
  Ship(int i, Location square, boolean horv) throws OffBoardException {
    size = Game.SHIPDATA[i].length;
    locations = new Location[size];  // make an array of this size
    name = Game.SHIPDATA[i].name;
    //System.out.print("Placing " + name + " at "); // TRACE
    for (int s = 0; s < size; s++) {
      // calculate the squares for this ship
      //System.out.print("(" + square.col + "," + square.row + ") "); // TRACE
      if (square.offBoard())
        throw new OffBoardException(name);
      locations[s] = square;
      square = square.next(horv);
    }
    //System.out.println(); // TRACE
  }
  
  boolean occupies(Location loc) {
    for (int l = 0; l < locations.length; l++) {
      if (locations[l].equals(loc))
	return true;
    }
    return false;
  }
  
  boolean isSunk(Field field) {
    //System.out.println("Is " + name + " sunk?");
    for (int l = 0; l < size; l++) {
      Location loc = locations[l];
      int horm = field.getHitOrMiss(loc.row, loc.col);
      //System.out.println("ship.isSunk() horm = " + horm);
      if (horm == Square.MISS || horm == Square.UNTRIED)
	return false;
    }
    System.out.println(name + " is sunk (ship.isSunk())");
    return true;
  }
}

class Fleet {
  Ship[] ships = { null, null, null, null, null };
  
  Ship getShip(int i) { return ships[i]; }
  boolean isComplete() { return ships[4] != null; }
  
  void addShip(int i, Location square, boolean horv)
       throws OffBoardException, OverlapException {
	 Ship newShip;
	 try {
	   newShip = new Ship(i, square, horv);
	 }
	 catch (OffBoardException e) {
	   ships[i] = null;
	   throw e;
	 }
	 if (overlapsAnyOther(newShip))
	   throw new OverlapException(newShip.name);
	 else
	   ships[i] = newShip;
  }
  
  boolean overlapsAnyOther(Ship s) {
    int n = 0;
    boolean overlaps = false;
  out: while (ships[n] != null) {
    for (int i = 0; i < s.locations.length; i++)
      for (int j = 0; j < ships[n].locations.length; j++)
	if (s.locations[i].equals(ships[n].locations[j])) {
	  overlaps = true;
	  break out;
	}
    ++n;
  }
  return overlaps;
  }
  
  void clear() {
    for (int s = 0; s < ships.length; s++)
      ships[s] = null;
  }
  
  Ship hit(Location sq) {
    // is there a ship in square sq?
    for (int s = 0; s < ships.length; s++) {
      Ship ship = ships[s];
      if (ship.occupies(sq))
	return ship;
    }
    return null;
  }

  boolean isSunk(Field field) {
    //System.out.println("Fleet sunk?");
    // are all the ships sunk?
    for (int s = 0; s < ships.length; s++) {
      if (ships[s] == null)
        return false; // this is because we can't check a net
                      // player's fleet
      if (!ships[s].isSunk(field))
	return false;
      else
	System.out.println(Game.SHIPDATA[s].name + " is sunk");
    }
    return true;
  }
}

class Location {
  int row;
  int col;
  
  Location(int c, int r) {
    row = r;
    col = c;
  }

  boolean equals(Location t) {
    return row == t.row && col == t.col;
  }

  Location next(boolean horv) {
    if (horv)
      return new Location(col + 1, row);
    else
      return new Location(col, row + 1);
  }

  String getGrid() {
    return new String("square " + (char)('A' + col) + (int)(row + 1));
  }

  boolean offBoard() {
    if (row >= DisplayBoard.NSQUARES || col >= DisplayBoard.NSQUARES)
      return true;
    else
      return false;
  }
  
  static Location getLoc(String in) {
    // this gets the location from <any> <x> <y>
    int i1 = in.lastIndexOf(" ");
    int y = Integer.parseInt(in.substring(i1 + 1));
    in = in.substring(0, i1);
    int i2 = in.lastIndexOf(" ", i1);
    int x = Integer.parseInt(in.substring(i2 + 1, i1));
    //System.out.println(in + " has location " + x + "," + y);
    return new Location(x, y);
  }
}

class Square {
  // a recording field is a grid of squares
  Ship ship;
  Location loc;
  static final int HIT = 1;
  static final int MISS = 0;
  static final int UNTRIED = -1;
  int hitOrMiss; // 0 = miss, 1 = hit, -1 = untried

  Square(int row, int col) {
    loc = new Location(col, row);
    hitOrMiss = UNTRIED;
  }

  void setHit() {
    hitOrMiss = HIT;
  }

  void setMiss() {
    hitOrMiss = MISS;
  }

  void setUntried() {
    hitOrMiss = UNTRIED;
}

  void setSunk(String name) {
    System.out.println("Sinking ");
    for (int n = 0; n < 5; n++)
      if (Game.SHIPDATA[n].name.equals(name)) {
	hitOrMiss = Game.SHIPBASE + n;
	System.out.println(name + hitOrMiss);
	break;
      }
  }
}

class ConnectState {
   private static final int STARTUP = 0;
   private static final int LIST = 1;
   private static final int CONNECT = 2;
   private static final int PLAY = 3;

   int state = STARTUP;
   int playno = 1;
   String name;
   DisplayFrame frame; // parent for dialogs

   ConnectState(String name, DisplayFrame frame) {
      this.name = name;
      this.frame = frame;
   }

   public String processInput(String input) throws IOException, QuitException {
      String command = input;
      String rest = null;
      int ind;
      DataInputStream stdIn = new DataInputStream(System.in);
      String output;

      ind = input.indexOf(" ");
      if (ind != -1) {
         command = input.substring(0, ind);
         rest = input.substring(ind + 1);
      }
      else
         command = input;

      switch (state ) {
         case STARTUP:
            if (command.equals("name?")) {
               state = LIST;
               output = "nameis " + name;
               return output;
            }
            else if (command.equals("quit"))
	      throw new QuitException("new");
	    else
               return null;
         case LIST:
            if (command.equals("guess?")) {
               state = PLAY;
               return "guess 3 5"; /******************/
            }
            else if (command.equals("connect?") || command.equals("connect")) {
               ConnectDialog d = new ConnectDialog(frame, rest);
               d.show();
               String answer = d.getAnswer();
               if (answer.equals("yes"))
                  state = PLAY;
               return answer;
            }
            else if (command.equals("list")) {
               if (rest == null || rest.equals(""))
                  return "choose none";
               NameListDialog d = new NameListDialog(frame, rest);
               d.show();
               String response = d.getResponse();
               return "choose " + response;
            }
            else if (command.equals(""))
               return "new";
            else if (command.equals("quit?")) {
	      // put up the play again dialog
	      // if yes, return new, if no, just exit (gracefully)
	      PlayAgainDialog playAgain = new PlayAgainDialog(frame);
	      playAgain.show();
	      if (playAgain.getAnswer())
		return "new";
	      else
		throw new QuitException("quit");
	    }
            else if (command.equals("quit"))
	      throw new QuitException("quit");
	    else {
	      state = PLAY;
	      return input; // we got a guess
	    }
         case PLAY:
	   if (command.equals("quit?")) {
	      // put up the play again dialog
	      // if yes, return new, if no, just exit (gracefully)
	      PlayAgainDialog playAgain2 = new PlayAgainDialog(frame);
	      playAgain2.show();
	      if (playAgain2.getAnswer())
		return "new";
	      else
		throw new QuitException("quit");
	   }
	   else if (command.equals("quit"))
	     throw new QuitException("quit");
	   else
	     return input;
      }
      return null; // keep the compiler quiet
   }
}


// exceptions
class OffBoardException extends Exception {
  OffBoardException() { super(); }
  OffBoardException(String s) { super(s); }
}

class OverlapException extends Exception {
  OverlapException() { super(); }
  OverlapException(String s) { super(s); }
}

class QuitException extends Exception {
  QuitException() { super(); }
  QuitException(String s) { super(s); }
}



