/*
    Battleship - Copyright (C) 2004 Jim Casaburi released under the GPL (which follows)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.net.*;
import java.io.*;
public class BattleshipGame extends javax.swing.JFrame  
{
    private Thread clientthread;
    private Thread serverthread;
    private JPanel panel = new JPanel();
    private JPanel board1 = new JPanel();
    private JPanel board2 = new JPanel();
    private JTextField ipaddress = new JTextField("127.0.0.1");
    private JTextField listenon = new JTextField("12345");
    private JTextField broadcaston = new JTextField("12346");
    private JLabel[][] board1labels;
    private JButton[][] board2buttons;
    private JButton exitbutton = new JButton("Exit");
    private JButton startnetwork = new JButton("Start Network");
    private JButton sendrequest = new JButton("Play Game");
    private JLabel[] horizontallabels1;
    private JLabel[] horizontallabels2;
    private JLabel[] verticallabels1;
    private JLabel[] verticallabels2;
    private JLabel yourboardlabel = new JLabel("Your side");
    private JLabel theirboardlabel = new JLabel("Your enemy");
    private JLabel ipaddresslabel = new JLabel("The enemy IP address");
    private JLabel listenonlabel = new JLabel("Listen port");
    private JLabel broadcastonlabel = new JLabel("Broadcast port");
    private JLabel enemystatus = new JLabel("Enemy: 4 boats remain");
    private JLabel programstatus = new JLabel("No connection to the network");
    private boardposition[][] myboard;
    private boardposition[][] theirboard;
    private ship[] myships;
    private int myshipcount = 4;
    private int theirshipcount = 4;
    private boolean playinggame = false;
    private boolean ismyturn = false;
    private int wantedx = -1;
    private int wantedy = -1;    
    public BattleshipGame() {
        super("Battleship Simulator");
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {}
        myboard = new boardposition[10][10];
        theirboard = new boardposition[10][10];
        myships = new ship[5];        
        board1labels = new JLabel[10][10];
        board2buttons = new JButton[10][10];
        verticallabels1 = new JLabel[10];
        horizontallabels1 = new JLabel[10];
        verticallabels2 = new JLabel[10];
        horizontallabels2 = new JLabel[10];
        // have the program actually exit when this window is closed
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        for (int a = 0; a < 10; a++) {
            verticallabels1[a] = new JLabel(""+(a+1));
            verticallabels2[a] = new JLabel(""+(a+1));
            horizontallabels1[a] = new JLabel(""+(a+1));
            horizontallabels2[a] = new JLabel(""+(a+1));
            for (int b = 0; b < 10; b++) {
                myboard[a][b] = new boardposition(a,b);
                theirboard[a][b] = new boardposition(a,b);
                board2buttons[a][b] = new JButton("");
                board1labels[a][b] = new JLabel("");
                board1labels[a][b].setOpaque(true);
                board1labels[a][b].setBackground(Color.BLUE);
                board1labels[a][b].setBorder(BorderFactory.createEtchedBorder());
                board2buttons[a][b].setBackground(Color.BLUE);
            }
        }
        setSize(500,400);
        this.getContentPane().setLayout(null);
        board1.setLayout(new GridLayout(10,10,1,1));
        board2.setLayout(new GridLayout(10,10,1,1));
        for (int a = 0; a < 10; a++)
        for (int b = 0; b < 10; b++) {
            board1.add(board1labels[a][b]);
            board2.add(board2buttons[a][b]);
            board2buttons[a][b].addActionListener(new BoardButtonHandler(a+1,b+1));
        }
        this.setResizable(false);
        exitbutton.addActionListener(new ExitButtonHandler());
        startnetwork.addActionListener(new starnetworkbuttonhandler());
        sendrequest.addActionListener(new sendrequesthandler());
        this.getContentPane().add(board1);
        board1.setBounds(20,40,200,200);
        this.getContentPane().add(board2);
        board2.setBounds(260,40,200,200);
        this.getContentPane().add(exitbutton);
        exitbutton.setBounds(400,340,80,30);
        this.getContentPane().add(ipaddress);
        ipaddress.setBounds(20,250,150,30);
        this.getContentPane().add(yourboardlabel);
        yourboardlabel.setBounds(20,0,80,20);
        this.getContentPane().add(theirboardlabel);
        theirboardlabel.setBounds(260,0,80,20);
        this.getContentPane().add(ipaddresslabel);
        ipaddresslabel.setBounds(20,280,150,20);
        this.getContentPane().add(listenon);
        listenon.setBounds(180,250,100,30);
        this.getContentPane().add(broadcaston);
        broadcaston.setBounds(300,250,100,30);
        this.getContentPane().add(listenonlabel);
        listenonlabel.setBounds(180,280,100,20);
        this.getContentPane().add(broadcastonlabel);
        broadcastonlabel.setBounds(300,280,100,20);
        this.getContentPane().add(startnetwork);
        startnetwork.setBounds(0,340,150,30);
        this.getContentPane().add(sendrequest);
        sendrequest.setBounds(150,340,150,30);
        this.getContentPane().add(enemystatus);
        this.getContentPane().add(programstatus);
        enemystatus.setBounds(20,310,200,20);
        programstatus.setBounds(240,310,200,20);
        for (int a = 0; a < 10; a++) {
            this.getContentPane().add(horizontallabels1[a]);
            this.getContentPane().add(horizontallabels2[a]);
            this.getContentPane().add(verticallabels1[a]);
            this.getContentPane().add(verticallabels2[a]);
            horizontallabels1[a].setBounds(a*20+20,20,20,20);
            horizontallabels2[a].setBounds(a*20+260,20,20,20);
            verticallabels1[a].setBounds(0,40+20*a,20,20);
            verticallabels2[a].setBounds(240,40+20*a,20,20);
        }
        sendrequest.setEnabled(false);
        layships(myships,myboard);
        setVisible(true);
    }
    public void o(String s) {
        JOptionPane.showMessageDialog(null,s);
    }
    public void gameplay(BufferedReader input, PrintWriter output) {
        String incoming;
        String result;
        while (myshipcount > 0 && theirshipcount > 0) {
            if (ismyturn) {
                programstatus.setText("Waiting for the button click");
                try {
                // keep on looping until the user clicks a button (which will set ismyturn
                // to be false
                clientthread.setPriority(Thread.MIN_PRIORITY);
                while (ismyturn) Thread.yield();
                // send the coordinates that you want over to the other player
                output.println(""+wantedx + " " +wantedy);
                output.flush();
                incoming = input.readLine();
                StringTokenizer s = new StringTokenizer(incoming);
                result = s.nextToken();    
                if (result.equals("HIT")) {
                    programstatus.setText("HIT!");
                    theirboard[wantedx][wantedy].hasahit = true;
                    theirboard[wantedx][wantedy].hasship = true;
                    board2buttons[wantedx][wantedy].setBackground(Color.RED);
                    if (s.hasMoreTokens()) {
                        theirshipcount--;
                        if (theirshipcount > 0) {
                        enemystatus.setText("Enemy: "+theirshipcount+" boats remain");
                        } else {
                            enemystatus.setText("You have vanquished your cursed enemy!");
                        }
                    }
                }
                else {
                    theirboard[wantedx][wantedy].hasahit = true;
                    board2buttons[wantedx][wantedy].setBackground(Color.BLACK);
                }
                }
                catch (Exception e) {
                    programstatus.setText(e.getMessage());
                }
                if (theirshipcount > 0) programstatus.setText("Waiting for the enemy...");
            }
            else {
                try {
                    incoming = input.readLine();
                    boolean theygotakill = false;
                    StringTokenizer s = new StringTokenizer(incoming);
                    int theirx = Integer.parseInt(s.nextToken());
                    int theiry = Integer.parseInt(s.nextToken());
                    boolean washit = false;
                    for (int a = 0; a < 4; a++) {
                        if (myships[a].contains(theirx,theiry)) {
                            washit = true;
                            myships[a].health--;
                            if (myships[a].health == 0) {
                                theygotakill = true;
                                myshipcount--;
                            }
                        }
                    }
                    if (washit) {
                        board1labels[theirx][theiry].setBackground(Color.RED);
                        if (theygotakill) output.println("HIT SHIP");
                        else output.println("HIT");
                        output.flush();
                        if (myshipcount == 0)
                            programstatus.setText("You have been vanquished!!");
                    }
                    else {
                        output.println("MISS");
                        output.flush();
                        board1labels[theirx][theiry].setBackground(Color.BLACK);
                    }                    
                } catch (Exception e) {
                    programstatus.setText(e.getMessage());
                }
                if (myshipcount > 0) {
                    programstatus.setText("Choose your next attack");
                    ismyturn = true;
                }
            }
        }
    }
    public static void main (String[ ] args)
    {    
        new BattleshipGame();
    }
    private void layships(ship[] ships, boardposition[][] board) {
        int tryx;
        int tryy;
        boolean tryhorizontal;
        for (int a = 5; a > 1; a--) {
            boolean laidship = false;
            while (!laidship) {
                if (Math.random() >= .5) {
                    tryhorizontal = true;
                    tryx = (int) ((Math.random() * (10-a)));
                    tryy = (int) (Math.random() * 10);
                    if (canlayship(tryx,tryy,a,tryhorizontal,board)) {
                        laidship = true;
                        ships[a-2] = new ship(a,tryx,tryy,tryhorizontal);
                        for (int b = 0; b < a; b++) {
                            board[tryx+b][tryy].hasship = true;
                            switch(a) {
                                case 5:
                                board1labels[tryx+b][tryy].
                                    setBackground(Color.ORANGE);
                                break;
                                case 4:
                                board1labels[tryx+b][tryy].
                                    setBackground(Color.GRAY);
                                break;
                                case 3:
                                board1labels[tryx+b][tryy].
                                    setBackground(Color.CYAN);
                                break;
                                default:
                                board1labels[tryx+b][tryy].
                                    setBackground(Color.GREEN);
                                break;
                                }                                
                            }
                        }
                } else {
                    tryhorizontal = false;
                    tryx = (int) (Math.random() * 10);
                    tryy = (int) ((Math.random() * (10-a)));
                    if (canlayship(tryx,tryy,a,tryhorizontal,board)) {
                        laidship = true;
                        ships[a-2] = new ship(a,tryx,tryy,tryhorizontal);
                        for (int b = 0; b < a; b++) {
                            board[tryx][tryy+b].hasship = true;
                            switch(a) {
                                case 5:
                                board1labels[tryx][tryy+b].
                                    setBackground(Color.ORANGE);
                                break;
                                case 4:
                                board1labels[tryx][tryy+b].
                                    setBackground(Color.GRAY);
                                break;
                                case 3:
                                board1labels[tryx][tryy+b].
                                    setBackground(Color.CYAN);
                                break;
                                default:
                                board1labels[tryx][tryy+b].
                                    setBackground(Color.GREEN);
                                break;
                                }
                        }
                    }
                }
            }
        }
    }
    private boolean canlayship(int cornerx, int cornery, int size,
                    boolean horizontal, boardposition[][] board) {
        if (cornerx < 0 || cornery < 0) return false;
        if (horizontal) {
            if (cornerx + size-1 > 9) return false;
        }
        else {
            if (cornery + size-1 > 9) return false;
        }
        for (int a = 0; a < size; a++) {
            if (board[horizontal?cornerx+a:cornerx][horizontal?cornery:cornery+a].hasship == true)
                return false;
        }
        return true;
    }
    private class sendrequesthandler implements ActionListener {
        public void actionPerformed(ActionEvent ev) {
            try {
            Thread t = new Thread(new sendtonetworkthread());
            clientthread = t;
            t.start();
            }
            catch (Exception e) {
                programstatus.setText(e.getMessage());
            }
        }
    }
    private class sendtonetworkthread implements Runnable {
        public void run() {
            BufferedReader input;
            PrintWriter output;
            try {
            Socket client = new Socket(ipaddress.getText(),Integer.parseInt(broadcaston.getText()));
            if (!playinggame) {
                serverthread.interrupt();
                sendrequest.setEnabled(false);
                broadcaston.setEnabled(false);
                ipaddress.setEnabled(false);
                playinggame = true;
                input = new BufferedReader(new InputStreamReader(client.getInputStream()));
                output = new PrintWriter(client.getOutputStream());
                programstatus.setText("It is my turn!");
                ismyturn = true;
                gameplay(input,output);
                }
            } catch (Exception e) {
                programstatus.setText(e.getMessage());
            }
        }
    }
    private class waitfornetworkthread implements Runnable {
        private ServerSocket server;
        public waitfornetworkthread(ServerSocket s) {
            super();
            server = s;
        }
        public void run() {
            BufferedReader input;
            PrintWriter output;
            String indata;
            try {
            Socket client=server.accept();
            broadcaston.setEnabled(false);
            ipaddress.setEnabled(false);
            // if you're already playing, you don't care about the recieved message
            if (!playinggame) {
                input = new BufferedReader(new InputStreamReader(client.getInputStream()));
                output = new PrintWriter(client.getOutputStream());
                programstatus.setText("It is the turn of the enemy");
                sendrequest.setEnabled(false);
                playinggame = true;
                ismyturn = false;
                gameplay(input, output);
                }
            } catch (Exception e) {
                programstatus.setText(e.getMessage());
                return;
            }
        }
    }
    private class starnetworkbuttonhandler implements ActionListener {
        public void actionPerformed(ActionEvent ev) {
            ServerSocket server;
            try {
                server = new ServerSocket(Integer.parseInt(listenon.getText()),0);
                listenon.setEnabled(false);
                programstatus.setText("Listening to the network waiting...");            
                startnetwork.setEnabled(false);
                sendrequest.setEnabled(true);
                Thread t = new Thread(new waitfornetworkthread(server));
                serverthread = t;
                clientthread = t;
                t.start();
            } catch (Exception e) {
                programstatus.setText(e.getMessage());
                return;
            }
        }
    }
    private class BoardButtonHandler implements ActionListener {
        private int xpos;
        private int ypos;
        public BoardButtonHandler(int x, int y) {
            super();
            xpos = x;
            ypos = y;
        }
        public void actionPerformed(ActionEvent ev) {
            if (ismyturn) {
                if (!theirboard[xpos-1][ypos-1].hasahit) {
                    wantedy = ypos-1;
                    ismyturn = false;
                    clientthread.setPriority(Thread.NORM_PRIORITY);
                }
            }
        }
    }
    private class ExitButtonHandler implements ActionListener {
        public void actionPerformed(ActionEvent ev) {
            System.exit(0);
        }
    }
    private class coordinate {
        public int x;
        public int y;
        public coordinate(int newx, int newy) {
            x = newx;
            y = newy;
        }
        public boolean samecoord(coordinate that) {
            return x == that.x && y == that.y;
        }
    }
    private class ship {
        public int shipsize;
        public int health;
        public int cornerx;
        public int cornery;
        public coordinate[] coordinates;
        public boolean horizontalorientation;
        public ship(int size, int xpos, int ypos, boolean horizontal) {
            coordinates = new coordinate[size];
            shipsize = size;
            health = size;
            cornerx = xpos;
            cornery = ypos;
            horizontalorientation = horizontal;
            for (int a = 0; a < size; a++) {
                if (horizontal) {
                    coordinates[a] = new coordinate(xpos + a, ypos);
                } else coordinates[a] = new coordinate(xpos, ypos+a);
            }
            }
        public boolean contains(int x, int y) {
            coordinate acord = new coordinate(x, y);
            for (int a = 0; a < shipsize; a++)
                if (coordinates[a].samecoord(acord)) return true;
            return false;
        }
}
    private class boardposition {
        public int xpos;
        public int ypos;
        public boolean hasship = false;
        public boolean hasahit = false;
        public boardposition(int x, int y) {
            xpos = x;
            ypos = y;
        }
    }
}