资源描述
实验项目 五子棋网络对战和聊天 实验日期 20160406
实验报告要求:
一、 实验目的:
学习和使用socket编程,熟练软件开发
二、 实验原理:
使用socket进行网络通信,java作为编程语言
三、 实验要求:
编写五子棋程序可以实现联机网络对战,并且可以进行聊天
四、 实验步骤、结果(程序+注释+截图)及分析:
首先拟定编程语言与开发方案,选择java语言,考虑到java可以跨平台运行,
然后决定把这个程序拆分为客户端、服务器两个部分,每个部分再分成5个小的部分实现不同功能。
1、然后考虑使用java的swing包,创建ClientChessPanel类负责棋盘部分,包括判断输赢,使用数组chesses[i][j]记录棋盘上棋子的分布,对数组进行不同的赋值表示网格节点上无棋、黑棋、白棋;使用playChessHandler作为鼠标单击事件,单击事件调用Clientskt中的函数传送棋子坐标以及输赢信息。drawChess函数画棋子,drawGrids画网格,gameOver判断棋盘棋子分布,输赢情况。
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import ChatOneToOneClient.Clientskt;
class ClientChessPanel extends JPanel{
private static final long serialVersionUID = 1L;
private int space=20; //网格间的距离
private int grids=30; //棋盘的网格数
private int radius=space/2; //棋的半径
Clientskt skt;
//当chesses[i][j]=0,表示网格节点(i,j)上无棋
//当chesses[i][j]=1,表示网格节点(i,j)上放白棋
//当chesses[i][j]=2,表示网格节点(i,j)上放黑棋
private int[][] chesses=new int[grids+1][grids+1];
private int currColor=1; //当前棋的颜色
private MouseListener playChessHandler=new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(skt.reMouseGo()){
int x=e.getX();
int y=e.getY();
//放一颗棋子
if(x<=grids*space && x>=0 && y<=grids*space && y>=0)
if(chesses[round(x)][round(y)]==0){
chesses[round(x)][round(y)]=currColor;
repaint(); //刷新图形
skt.dataout("x:"+String.valueOf(round(x)));
skt.dataout("y:"+String.valueOf(round(y)));
skt.setMouseGo(false);
if(gameOver(currColor)){
skt.dataout("g:你输了");
skt.chat.clientDialog=new ClientMyDialog(skt.chat,"你赢了");
skt.chat.clientDialog.setVisible(true);
}
currColor=currColor==1?2:1; //切换棋子的颜色
}
}
}
};
public int round(float a){ //获得接近a的网格节点坐标
float f=a/space;
return Math.round(f);
}
public ClientChessPanel(int space,int grids,Clientskt skt){
this.space=space;
this.grids=grids;
this.radius=space/2;
this.skt=skt;
setBackground(Color.BLUE);
setSize(space*grids,space*grids);
addMouseListener(playChessHandler);
startChess();
}
public void startChess(){
clearGrids(); //清空棋盘
currColor=1;
repaint(); //刷新图形
}
private void clearGrids(){
for(int i=0;i<=grids;i++)
for(int j=0;j<=grids;j++)
chesses[i][j]=0;
}
//画一颗棋子
private void drawChess(Graphics g,int x,int y,int color){
g.setColor(color==1?Color.GREEN:Color.BLACK);
g.fillOval(x*space-radius,y*space-radius,radius*2,radius*2);
}
//画网格
private void drawGrids(Graphics g){
g.setColor(Color.DARK_GRAY);
for(int i=0;i<=grids;i++){
g.drawLine(0,i*space,grids*space,i*space);
g.drawLine(i*space,0,i*space,grids*space);
}
}
//接收对方下的棋坐标
public void paintChess(int x,int y){
if(x<=grids*space && x>=0 && y<=grids*space && y>=0){
if(chesses[x][y]==0){
chesses[x][y]=currColor;
currColor=currColor==1?2:1; //切换棋子的颜色
skt.setMouseGo(false);
skt.setMouseGo(true);
repaint(); //刷新图形
}
}
}
//判断游戏是否结束
public boolean gameOver(int gameOver){
int five=0;//用于判断是否有连续5个子
for(int i=0;i<grids;i++){//行
for(int j=0;j<grids;j++){//列
if(chesses[i][j]==gameOver){
five++;
for(int k=1;k<5&&j<grids-4;k++){//行向比较
if(chesses[i][j+k]==gameOver){
five++;
if(five==5){
return true;
}
}
else{
five=1;
k=5;
}
}
for(int k=1;k<5&&i<grids-4;k++){//列向比较
if(chesses[i+k][j]==gameOver){
five++;
if(five==5){
return true;
}
}
else{
five=1;
k=5;
}
}
for(int k=1;k<5&&i<grids-4&&j<grids-4;k++){//右斜向比较
if(chesses[i+k][j+k]==gameOver){
five++;
if(five==5){
return true;
}
}
else{
five=1;
k=5;
}
}
for(int k=1;k<5&&i<grids-4&&j>4;k++){//左斜向比较
if(chesses[i+k][j-k]==gameOver){
five++;
if(five==5){
return true;
}
}
else{
five=1;
k=5;
}
}
}
}
five=0;
}
return false;
}
public void paintComponent(Graphics g){ //覆盖paintComponent()方法
super.paintComponent(g); //必须先调用父类的方法
drawGrids(g); //画网格
for(int i=0;i<=grids;i++)
for(int j=0;j<=grids;j++)
if(chesses[i][j]!=0)
drawChess(g,i,j,chesses[i][j]); //画棋子
}
}
2、 ClientComponentPopupMenu类主要负责聊天的部分,使用JTextField并且对其添加单击事件以及鼠标事件,可以实现文本的剪贴、复制粘贴等功能。
import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import javax.swing.*;
import javax.swing.text.*;
public class ClientComponentPopupMenu extends JPopupMenu implements MouseListener,ActionListener{
private static final long serialVersionUID = 1L;
public static ClientComponentPopupMenu sharedInstance = null;
public static void installToComponent(JTextComponent c){
if(c instanceof JTextArea && !(c instanceof JPasswordField)){
c.addMouseListener(ClientComponentPopupMenu.getSharedInstance());
}
else if(c instanceof JTextField && !(c instanceof JPasswordField)){
c.addMouseListener(ClientComponentPopupMenu.getSharedInstance());
}
}
public static void uninstallFromComponent(JTextComponent c){
if(c instanceof JTextArea && !(c instanceof JPasswordField)){
c.removeMouseListener(getSharedInstance());
}
else if(c instanceof JTextField && !(c instanceof JPasswordField)){
c.removeMouseListener(getSharedInstance());
}
}
JMenuItem cutItem,copyItem,pasteItem,deleteItem,selectAllItem,printItem;
//构造器
public ClientComponentPopupMenu(){
add(cutItem = new JMenuItem("剪切 (T)"));
add(copyItem = new JMenuItem("复制 (C)"));
add(pasteItem = new JMenuItem("粘贴 (P)"));
add(deleteItem = new JMenuItem("删除 (D)"));
addSeparator();
add(selectAllItem = new JMenuItem("全选 (A)"));
addSeparator();
add(printItem = new JMenuItem("打印 (R)"));
//设置快捷键
cutItem.setMnemonic('T');
copyItem.setMnemonic('C');
pasteItem.setMnemonic('P');
deleteItem.setMnemonic('D');
selectAllItem.setMnemonic('A');
printItem.setMnemonic('R');
//增加监听
cutItem.addActionListener(this);
copyItem.addActionListener(this);
pasteItem.addActionListener(this);
deleteItem.addActionListener(this);
selectAllItem.addActionListener(this);
printItem.addActionListener(this);
}
//如果对象不存在,创建一个对象
public static ClientComponentPopupMenu getSharedInstance(){ //单例模式
if (sharedInstance == null){
sharedInstance = new ClientComponentPopupMenu();
}
return sharedInstance;
}
public void mouseReleased(MouseEvent e){
if(e.isPopupTrigger()&&e.getSource() instanceof JTextArea){ //e.isPopupTrigger()鼠标右键
JTextArea textfield = (JTextArea) e.getSource();
if(Boolean.TRUE.equals(textfield.getClientProperty("DisablePopupMenu"))){
return;
}
textfield.requestFocusInWindow();
this.show(textfield,e.getX(),e.getY()); //获得组件及位置
}
else if(e.isPopupTrigger()&&e.getSource() instanceof JTextField){
JTextField textfield = (JTextField) e.getSource();
if(Boolean.TRUE.equals(textfield.getClientProperty("DisablePopupMenu"))){
return;
}
textfield.requestFocusInWindow();
this.show(textfield,e.getX(),e.getY()); //获得组件及位置
}
}
public void mouseClicked(MouseEvent e){
}
public void mousePressed(MouseEvent e){
}
public void mouseEntered(MouseEvent e){
}
public void mouseExited(MouseEvent e){
}
public void show(Component invoker,int x,int y){
JTextComponent tc = (JTextComponent) invoker;
String sel = tc.getSelectedText();
boolean selected = sel != null&& !sel.equals("");
boolean enableAndEditable = tc.isEnabled()&&tc.isEditable();
//禁用与启用
cutItem.setEnabled(selected && enableAndEditable);
copyItem.setEnabled(selected && tc.isEnabled());
deleteItem.setEnabled(selected && enableAndEditable);
pasteItem.setEnabled(enableAndEditable);
selectAllItem.setEnabled(tc.isEnabled());
super.show(invoker,x,y);
}
public void actionPerformed(ActionEvent e){
JTextComponent tc =(JTextComponent) getInvoker();
if(e.getSource() == cutItem){
tc.cut();
}
else if(e.getSource() == copyItem){
tc.copy();
}
else if (e.getSource() == pasteItem){
tc.paste();
}
else if (e.getSource() == selectAllItem){
tc.selectAll();
}
else if (e.getSource() == deleteItem){
Document doc = tc.getDocument();
int start = tc.getSelectionStart();
int end = tc.getSelectionEnd();
try{
Position p0 = doc.createPosition(start);
Position p1 = doc.createPosition(end);
if((p0 != null) && (p1 !=null)&&(p0.getOffset()!=p1.getOffset())){
doc.remove(p0.getOffset(),p1.getOffset() - p0.getOffset());
}
}
catch(BadLocationException be){
}
}
if(e.getSource() == printItem ){
try {
tc.print();
} catch (PrinterException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
3、 ClientMyDialog类是游戏结束时的提示对话框。
public class ClientMyDialog extends JDialog{
private static final long serialVersionUID = 1L;
private JLabel label1 ;
private JButton jbtn=new JButton("确定");
public ClientMyDialog(JFrame parent,String info) {
super(parent, "客户端提示信息", true);
label1=new JLabel(info);
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(label1,BorderLayout.NORTH);
contentPane.add(jbtn,BorderLayout.SOUTH);
jbtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
dispose(); // 关闭对话框
}
});
setLocation(300, 300);
pack();
}
}
4、 Clientskt是网络通信部分,首先客户端要输入ip地址、端口号,theInputStream和theOutputStream传输流信息。
public class Clientskt extends Thread{
Socket skt;
InetAddress host;
int port;
BufferedReader theInputStream;
PrintStream theOutputStream;
String readin;
ChatOneToOneClient chat;
int x=-1,y=-1;//用于接收坐标
private boolean mouseGo=false;//使棋盘能放棋子
public Clientskt(String ip,int p,ChatOneToOneClient chat){
try {
host=InetAddress.getByName(ip);
port=p;
this.chat=chat;
} catch (Exception e) {
// TODO: handle exception
chat.ta.append(e.toString());
}
}
public void run(){
try {
chat.ta.append("尝试联机\n");
skt=new Socket(host,port);
chat.ta.append("联机成功\n");
theInputStream=new BufferedReader(new InputStreamReader(skt.getInputStream()));
theOutputStream=new PrintStream(skt.getOutputStream());
chat.tftype.setEditable(true);
mouseGo=true;
while(true){
readin=theInputStream.readLine();
if(readin.startsWith("chat:")){
readin=readin.substring(5, readin.length());
chat.ta.append(readin+"\n");
chat.ta.setCaretPosition(chat.ta.getText().length());
}
else if(readin.startsWith("g:")){
readin=readin.substring(2, readin.length());
mouseGo=false;
chat.clientDialog=new ClientMyDialog(chat,readin);
chat.clientDialog.setVisible(true);
}
else{
if(readin.startsWith("x:")){
readin=readin.substring(2, readin.length());
x=Integer.parseInt(readin);
}
else{
readin=readin.substring(2, readin.length());
y=Integer.parseInt(readin);
}
if(x!=-1&&y!=-1){
chat.chessPanel.paintChess(x, y);
x=-1;
y=-1;
}
}
}
}
catch (SocketException e) {
// TODO: handle exception
chat.ta.append("联机中断");
chat.clientBtn.setEnabled(true);
chat.tfaddress.setEditable(true);
try {
skt.close();
}
catch (IOException err) {
// TODO: handle exception
chat.ta.append(err.toString());
}
}catch(IOException e){
chat.ta.append(e.toString());
}
}
public void dataout(String data){
theOutputStream.println(data);
}
public boolean reMouseGo(){
return mouseGo;
}
public void setMouseGo(boolean MouseGO){
this.mouseGo=MouseGO;
}
}
5、 ChatOneToOneClient类布局整体程序,采用BorderLayout进行布局,其中tftype作为聊天输入框,对其添加键盘事件。
服务器端程序类似于客户端。
public class ChatOneToOneClient extends JFrame{
private static final long serialVersionUID = 1L;
ClientMyDialog clientDialog;
Container contentPane;
JButton clientBtn;
JTextArea ta;
JLabel inputIP,myName;
JTextField tfaddress,tfport,tftype;
int port;
Clientskt client;
static ChatOneToOneClient frm;
JScrollPane pane ;
JPanel north,south,center,east,west;
ClientChessPanel chessPanel;
public ChatOneToOneClient(){
contentPane=getContentPane();
north=new JPanel();
south=new JPanel();
center=new JPanel();
east=new JPanel();
west=new JPanel();
inputIP=new JLabel("请输入IP:");
myName=new JLabel("某某,25565");
clientBtn=new JButton("连接");
ta=new JTextArea(20,15);
tfaddress=new JTextField(9);
tfport=new JTextField("8888");
tfport.setEditable(false);
tftype=new JTextField(50);
pane=new JScrollPane(ta);
ta.setEditable(false);
tftype.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-genera
展开阅读全文