游戏中的联机作战事实上比人机作战更为刺激有趣富有人情味,毕竟人脑胜于电脑,只是因为硬件的普及率太低所以才没有市场。本文在这里将以象棋为例介绍Java是如何实现网络联机游戏的。
首先需要声明的是,本文所介绍的联机原理并不同于互联网站上的许多联机方法,是当前特别流行的客户机/服务器(C/S)模式。其次本文并不涉及智能的部分,即只介绍“人人大战”,而不去理会“人机作战”。
一、象棋原理
如果我不先把象棋实现的原理向读者做一下简要介绍,那么在后面的数据传输部分将会有一种纠缠不清的感觉。
游戏中的五彩缤纷的物体背后其实仅仅是一个数字,这个数字是物体的唯一标示,象棋的原理也是一样。32个棋子必须用不同的数字来标记。为了方便记忆与循环操作,本文中,每一个棋子用一个十进制的三位数来表示,作为它的“代码”。最高位为0或2,表示红绿双方;最低位来区分开局时不同位置的相同棋子,“兵(卒)”用0,1,2,3,4来区分,其余用0或1;中间位用1-7这七个数字分别表示将(帅),车,跑,马,士,象(相),卒(兵),正应了“将一车二跑三马四士五象六兵七”那句俗语。这样绿“将”代码为10,红中“兵”代码为272,等等。接着建立一个9×10的二维数组来表示棋盘的格子,棋子代码在数组中的位置即为棋子在屏幕棋盘上位置。如开局数组为:
public static final int arrayBak[][]={//其中0表示没有棋子
{20,40,50,60,10,61,51,41,21},
{0,0,0,0,0,0,0,0,0},
{0,30,0,0,0,0,0,31,0},
{70,0,71,0,72,0,73,0,74},
{0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0},
{270,0,271,0,272,0,273,0,274},
{0,230,0,0,0,0,0,231,0},
{0,0,0,0,0,0,0,0,0},
{220,240,250,260,210,261,251,241,221}
};
……………….代码一………………
如此可以看出,一个棋子的属性由三部分组成,一是本身的代码(int code),另外两个为其在棋盘上的纵横位置(int x,int y),x,y皆从0开始数起。如开局时绿色左边“跑”的属性为(x=1,y=2,code=30)。
二、 棋盘与棋子的绘制
棋盘可以有木质石质甚至画屏等各种样式。本文采用背景与棋盘线分离的方法,好处是可以随意变换背景。首先找一些纹理作为背景,(PowerPoint有许多),然后用下面语句装载,接下来用“画矩形法”绘制棋盘线,获取如镌如刻的感觉,下面是部分代码:
int LcornerX,LcornerY;//棋盘的左上角坐标
int lengthX,lengthY;//棋盘的长度与宽度
int lengthMin;//棋格的最小宽度
MediaTracker mt=new MediaTracker (this);//跟踪图像
public void drawX(Graphics g,int centery)//画两条斜线
{
//注意这两条线不能使用画矩形法,代码从略
}
public void drawHright(Graphics g,int centerx,int centery)//画右爿形
{
g.fill3DRect (LcornerX+lengthX*centerx+6,LcornerY+lengthY*centery-6,
this.lengthMin /3-3,3,false);//画右上横
g.fill3DRect (LcornerX+lengthX*centerx+6,LcornerY+lengthY*centery-lengthMin /3,
3,this.lengthMin /3-3,false);//画右上竖
g.fill3DRect (LcornerX+lengthX*centerx+6,LcornerY+lengthY*centery+6,
this.lengthMin /3-3,3,false);//画右下横
g.fill3DRect (LcornerX+lengthX*centerx+6,LcornerY+lengthY*centery+6,
3,this.lengthMin /3-3,false);//画右下竖
}
public void drawHleft(Graphics g,int centerx,int centery)//画左爿形
{
/*参考画右爿形语句,代码从略*/
}
public void paint(Graphics g)
{//绘制棋盘
int imgwid=chessArray.img.getWidth(this);
int imghei=chessArray.img.getHeight(this);
chB();
if(mt.checkID (0))//若图像已加载完毕
{
g.drawImage(chessArray.img,0,0,getSize().width,getSize().height ,this);
if(First){
chessArray.information ("图像已加载完毕,请选择你的身份");
First=false;
}
}else return;
g.setColor(Color.lightGray);
g.fill3DRect(LcornerX,LcornerY,4,lengthY*9+4,false);//画左边线
for(int i=0;i<10;i++)//画横线
{
g.setXORMode (Color.black);
g.setFont (new Font ("",1,lengthX/2));
g.drawString (""+chCount[i],LcornerX/2+lengthX*9,LcornerY+lengthY*i+lengthY/3);
if(i==9)i=15;
g.drawString(""+(i+1),LcornerX+lengthX*i,LcornerY+lengthY*10);
g.setPaintMode ();
if(i==15)i=9;
g.fill3DRect(LcornerX,LcornerY+lengthY*i,lengthX*8+4,4,false);
}
g.fill3DRect(LcornerX+lengthX*8,LcornerY,4,lengthY*9+4,false);
//画右边线
for(int i=1;i<8;i++)//画竖线
{
g.fill3DRect(LcornerX+lengthX*i,LcornerY,4,lengthY*4+4,false);
g.fill3DRect(LcornerX+lengthX*i,LcornerY+lengthY*5,4,lengthY*4+4,false);
}
drawX(g,1);//画将位
drawX(g,8);//画帅位
for(int i=0;i<4;i++){//画兵卒位
drawHleft(g,(i+1)*2,3);
drawHright(g,i*2,3);
drawHleft(g,(i+1)*2,6);
drawHright(g,i*2,6);
}
for(int i=0;i<2;i++){//画炮位
/*参考画兵卒位语句,代码从略*/
}
drawCh ();
}
int gsize;
public void update(Graphics g)
{
mt.addImage (chessArray.img,0);
paint(g);
}………代码二………..
棋子的绘制牵扯到这样一个问题,即如何将一幅图片无背景(透明)显示。因为棋子是规则的几何形状,这里介绍另外一种独特又巧妙的方法:
// chessMan 绘制棋子
import java.awt.*;
import java.awt.image.*;
public class chessMan
{
int Diameter;
Color chC,backC;
String chS;
int X,Y,Num,Count;
Graphics g;
boolean judge=true;
ImageObserver ib;
public chessMan(Graphics gbak,ImageObserver ibBak)
{
ib=ibBak;
g=gbak;
}
public void chM(int bakX,int bakY,int dBak,String chSbak,Color chCbak,Color backCbak)
{//四个参数分别表示棋子圆的直径,字符,前景颜色,背景颜色
chS=chSbak;
chC=chCbak;
backC=backCbak;
X=bakX;
Y=bakY;
Diameter=dBak;
}
public void paint()
{
if(!judge)return;
g.setColor (backC);
g.fillOval (X,Y,Diameter,Diameter);
g.setXORMode (Color.black);
g.fillRect (X,Y,Diameter,Diameter);
if(chC==Color.red )
g.drawImage(chessArray.imgRed ,X,Y,Diameter,Diameter,ib);
if(chC==Color.blue )
g.drawImage(chessArray.imgBlue ,X,Y,Diameter,Diameter,ib);
g.setPaintMode ();
g.setColor(Color.yellow);
g.setFont(new Font("chequer",Font.BOLD,Diameter*2/3));
g.drawString(chS,X+Diameter/2-Diameter/3,Y+Diameter/2+Diameter/4);
}
public void repaint()
{
paint();
}
public void setVisible(boolean paintOR)
{
judge=paintOR;
paint();
}
}………代码三………..
上面这些代码必须依赖这样两个前提:一:图片的背景必须是黑色;二:棋子的直径等于图片的边长。如果您为了追求立体效果而将棋子变为椭圆形的话,那么也应使椭圆形的的长短轴分别等于矩形的长和宽。
三、棋子的移动与网络数据传输
和现实中下棋一样,上面的一番努力仅仅是把棋摆好了,真正要实现网上对弈还得对鼠标事件进行一番处理以实现棋子的移动。因为其源代码较为简单所以不做解释。
int numx,numy,numxBak,numyBak,numCh,chArraybak;//numch 棋子序列号;chArraybak 起点棋子代码
//numx 落点棋子横向代码; numy 落点棋子纵向代码; numxBak 起点棋子横向代码; numxBak 起点棋子纵向代码
int x=0,y=0,xbak=0,ybak=0;
public int judgeChoice(int ch) //判断选中的棋子的代码,ch是数组中的数值
{
for(int i=0;;i++)
if(ch==chequer[i].Num)
return i;
}
public boolean judgePlace(int evtx,int evty)
//判断点是否在棋子的圆内,若在返回真,否则返回假
{
numx=(evtx-LcornerX+lengthX/2)/lengthX;//判断选中棋子的行数
numy=(evty-LcornerY+lengthY/2)/lengthY;//判断选中棋子的列数
x=LcornerX+numx*lengthX;
y=LcornerY+numy*lengthY;
if(evtx>(x-(lengthX-6)/2)&&evtx<(x+(lengthX-6)/2)&&evty>(y-(lengthY-6)/2)&&evty<(y+(lengthY-6)/2))
return true;
else return false;
}
public void winOR(Graphics g)
{
g.setFont (new Font("",0,this.width /4));
if(chessArray.netChange )
g.drawString("你输了",0,this.height /2);
else
g.drawString("你嬴了",0,this.height /2);
}
public boolean walkCh(boolean NotGiveUp)
{
chArraybak=chessArray.chArray[numyBak][numxBak];//备份起点棋子代码
chessArray.chArray[numyBak][numxBak]=0;//删除起点棋子代码
if(chessArray.chArray[numy][numx]!=0)//若将要落子的地方有棋,则将其设为不可见
chequer[judgeChoice(chessArray.chArray[numy][numx])].setVisible(false);
int ch=chArraybak;
if(chessArray.chArray[numy][numx]==10||chessArray.chArray[numy][numx]==210)
{
chessArray.change=false;
winOR(this.getGraphics ());
}
numCh=judgeChoice(ch);
chessArray.chArray[numy][numx]=ch;//修改落点棋子代码
this.Bcount =false;
repaint(LcornerX+lengthX*numxBak-this.lengthMin/2,LcornerY+lengthY*numyBak-this.lengthMin/2,
lengthMin-1,lengthMin-1);//清除起点位置的棋子
chequer[numCh].chM(LcornerX+lengthX*numx-this.lengthMin/2,LcornerY+lengthY*numy-this.lengthMin/2,
lengthMin-1,chString[ch/200][ch%100/10-1],chColor[ch/200],backColor);
chequer[numCh].repaint();//将棋子颜色改为粉红色,一次走棋过程完成
return true;
}
|