Delphi开发基于UDP的网络五子棋
五子棋是一种家喻户晓的棋类游戏,它的多变吸引了无数的玩家。下面我来介绍我用Delphi6.0中文企业版制作的“网络五子棋”程序。
一、网络五子棋设计思想
先介绍一下程序原理。下棋需要有棋盘,程序中我们通过在窗体激活时在image1上TCanvas对象上画出了五子棋棋盘。有了五子棋棋盘还需要有棋子。我使用了image1上TCanvas对象上绘制圆形图案代表棋子。界面中要求用户选择作为主机还是联机,用server变量来保存。
我们设计的难点在于与对方需要通讯。这里我使用了基于UDP(User Data Protocol)的NMUDP控件。UDP是用户数据文报协议的简称,两台计算机之间的传输类似于传递邮件;两台之间没有明确的连接,使用UDP协议建立对等通信。这里虽然两者两台计算机不分主次,但我们设计时假设一台做主机(黑方),等待其他人加入。其他人想加入的时候输入主机的IP。为了区分通信中传送的是“从机IP信息”,“下的棋子位置信息”,“悔棋请求”等,在发送信息的首部加上代号。我如下定义了协议:
‘0’+从机IP
‘1’+下的棋子位置坐标(x,y)
‘2’ (联机成功)
‘3’+server(server代表胜方)
‘4’(表示要求悔棋)
‘5’+1或0(1表示同意,0表示不同意悔棋)
在下棋过程中,为了保存下过的棋子的位子使用了map数组,map数组初值为零,表示此处物棋子。map数组可以有1,2值,分别代表2种棋子图案,黑子,白子。为了显著表示对方刚下的棋子,这里我们用蓝子代表刚下过的黑子,红子代表刚下过的白子,其横纵坐标位置保存在old_qx,old_qy中。每下一步棋子,首先处理对(old_qx,old_qy)处对方刚下的棋子变色,将本步棋在棋盘上显示,并将本步棋的x,y坐标传出。每当接收到对方的下的棋子位置信息时,根据server判断画红子还是蓝子,并根据位置修改(old_qx,old_qy)。
本程序具有一定智能性,可以判断是否已经连成五子。程序中每下一步棋子,调用Gameover函数判断是否已经连成五子,如果返回非零,则说明已经连成五子,显示输赢结果。无论否已经连成五子,均将本步信息传到对方。Gameover函数中对棋盘的横竖斜方向均判断一遍,来达到检测出输赢。
二、窗体界面和具体的代码:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, NMUDP, ComCtrls,
winsock;
type
TForm1 = class(TForm)
Image1: TImage;
Image2: TImage;
Label1: TLabel;
Label2: TLabel;
GroupBox1: TGroupBox;
RadioButton1: TRadioButton;
RadioButton2: TRadioButton;
GroupBox2: TGroupBox;
Edit1: TEdit;
NMUDP1: TNMUDP;
StatusBar1: TStatusBar;
Button1: TButton;
Button2: TButton;
Button3: TButton;
function gameover(a:integer):integer;
procedure Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure RadioButton1Click(Sender: TObject);
procedure RadioButton2Click(Sender: TObject);
procedure NMUDP1DataReceived(Sender: TComponent; NumberBytes: Integer;
FromIP: String; Port: Integer);
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormActivate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
map:array[1..15,1..15] of integer;
server:integer;
old_qx,old_qy:integer;
implementation
{$R *.dfm}
//uses winsock
//取本机IP地址
function GetIP:String;
var
WSData: TWSAData;
Buffer: array[0..63] of Char;
HostEnt: PHostEnt;
PPInAddr: ^PInAddr;
IPString: String;
begin
IPString:='';
try
WSAStartUp($101, WSData);
GetHostName(Buffer, SizeOf(Buffer));
HostEnt:=GetHostByName(Buffer);
if Assigned(HostEnt) then
begin
PPInAddr:=@(PInAddr(HostEnt.H_Addr_List^));
while Assigned(PPInAddr^) do
begin
IPString:=StrPas(INet_NToA(PPInAddr^^));
Inc(PPInAddr);
end;
end;
Result := IPString;
finally
try
WSACleanUp;
except
end;
end;
end;
function TForm1.gameover(a:integer ):integer;//判断输赢
var i,j,s:integer;
begin
s:=0;
For i:= 1 To 11 do
For j:= 1 To 11 do
If (map[i,j] = a) and (map[i+1,j+1] = a) And (map[i+2,j+2] = a) And (map[i + 3,j + 3] = a )And (map[i + 4,j + 4] = a )Then s:= a; // Exit Function
For i:= 5 To 15 do
For j:= 1 To 11 do
If (map[i, j] = a) And (map[i - 1, j + 1] = a )And (map[i - 2, j + 2] = a )And (map[i - 3, j + 3] = a) And (map[i - 4, j + 4] = a )Then s:= a;
For i:= 1 To 15 do
For j:= 5 To 15 do
If (map[i, j] = a) And( map[i, j - 1] = a )And( map[i, j - 2] = a) And (map[i, j - 3] = a) And( map[i, j - 4] = a )Then s:= a;
For i:= 5 To 11 do
For j:= 1 To 15 do
If (map[i, j] = a) And( map[i + 1, j] = a )And (map[i + 2, j] = a) And( map[i + 3, j] = a) And( map[i + 4, j] = a )Then s:= a;
if s<>0 then
Result:=s
else
Result:=0;
End;
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var qx,qy:integer;
buf:array[1..20] of char;
begin
// label1.Caption :=inttostr(x-12)+' '+inttostr(y-12);
button3.Enabled :=true;
x:=x-13;
y:=y-13;
qx:=trunc(x/21)+1;
qy:=trunc(y/20)+1;
// label2.Caption :=inttostr(qx)+' '+inttostr(qy);
map[qx,qy]:=server;
//处理上一次的对方走的棋子颜色改变
if server=1 then //主机 黑方
begin
image1.Canvas.Brush.Color:=clWHITE;
image1.Canvas.pen.Color:=clWHITE;
image1.Canvas.Ellipse(old_qx*21-9,old_qy*20-9+1,old_qx*21+9,old_qy*20+9+1);
old_qx:=qx;old_qy:=qy;
end
else
begin
image1.Canvas.Brush.Color:=clBlack;
image1.Canvas.pen.Color:=clBlack;
image1.Canvas.Ellipse(old_qx*21-9,old_qy*20-9+1,old_qx*21+9,old_qy*20+9+1);
old_qx:=qx;old_qy:=qy;
end;
//处理本次的对方走的棋子
if server=1 then
begin
image1.Canvas.Brush.Color:=clBlack;
image1.Canvas.pen.Color:=clBlack;
image1.Canvas.Ellipse(qx*21-9,qy*20-9+1,qx*21+9,qy*20+9+1);
end
else
begin
image1.Canvas.Brush.Color:=clWHITE;
image1.Canvas.pen.Color:=clWHITE;
image1.Canvas.Ellipse(qx*21-9,qy*20-9+1,qx*21+9,qy*20+9+1);
end;
//继续传棋子信息
begin
buf[1]:='1';buf[2]:=CHR(qx); buf[3]:=CHR(qy);
NMUDP1.SendBuffer(buf,20);
end;
image1.Enabled:=false;//下过一步后,禁止再走棋
//判断输赢
if gameover(server)=server then
begin
label1.caption:='你赢了';
image1.Enabled:=false;
buf[1]:='3';buf[2]:=CHR(server);
NMUDP1.SendBuffer(buf,20);
button1.Enabled:=true;
button3.Enabled:=false; //悔棋按钮禁用
image1.Enabled:=false;
statusbar1.SimpleText:='你赢了,下棋结束,请重新开局';
end;
end;
procedure TForm1.RadioButton1Click(Sender: TObject);
begin
groupbox2.Visible:=false;
NMUDP1.LocalPort:=5555;
NMUDP1.RemotePort:=6666;
server:=1;
statusbar1.SimpleText:=getip+'正在等待联机';
end;
procedure TForm1.RadioButton2Click(Sender: TObject);
begin
groupbox2.Visible:=true;
end;
procedure TForm1.NMUDP1DataReceived(Sender: TComponent;
NumberBytes: Integer; FromIP: String; Port: Integer);
var qx,qy,i:integer;
buf:array[1..20] of char; str:string;
begin
button3.Enabled:=false; //悔棋按钮禁用
image1.Enabled:=true;
NMUDP1.ReadBuffer(buf,NumberBytes);
case buf[1] of
'0': //获取对方从机IP
begin
i:=2;
while(buf[i]<>#0)do
begin
str:=str+buf[i];i:=i+1;end;
NMUDP1.RemoteHost:=str;
buf[1]:='2';
NMUDP1.SendBuffer(buf,20);
statusbar1.SimpleText:='连接成功,请先走棋';
end;
'1': //对方走棋信息
begin
qx:=ord(buf[2]);
qy:=ord(buf[3]);
old_qx:=qx;old_qy:=qy;
//处理本次的对方走的棋子
if server=1 then
begin
image1.Canvas.Brush.Color:=clBLUE;
image1.Canvas.pen.Color:=clBLUE;
image1.Canvas.Ellipse(qx*21-9,qy*20-9+1,qx*21+9,qy*20+9+1);
end
else
begin
image1.Canvas.Brush.Color:=clRED;
image1.Canvas.pen.Color:=clRED;
image1.Canvas.Ellipse(qx*21-9,qy*20-9+1,qx*21+9,qy*20+9+1);
end;
statusbar1.SimpleText:='正在下棋中';
end;
'2': //主机发给从机的连接成功信息
begin
statusbar1.SimpleText:='连接主机成功,等待对方走棋';
end;
'3': //结束信息
begin
image1.Enabled:=false;
button1.Enabled:=true;
if ord(buf[2])=server then
label1.caption:='你赢了'
else
label1.caption:='你输了';
statusbar1.SimpleText:='下棋结束,请重新开局';
end;
'4'://悔棋处理
begin
if MessageDlg('对方要求悔棋,同意吗?',mtConfirmation, [mbYes, mbNo], 0) = mrYes then
begin //同意
image1.Canvas.Brush.Color:=image1.Canvas.Pixels[2,2]; //取棋盘颜色
image1.Canvas.pen.Color:=image1.Canvas.Pixels[2,2];
image1.Canvas.Ellipse(old_qx*21-9,old_qy*20-9+1,old_qx*21+9,old_qy*20+9+1);
buf[1]:='5';
buf[2]:='1';
NMUDP1.SendBuffer(buf,20);
end
else
begin //不同意
buf[1]:='5';
buf[2]:='0';
NMUDP1.SendBuffer(buf,20);
end
end;
'5':
begin
if buf[2]='1' then //同意
begin
image1.Canvas.Brush.Color:=image1.Canvas.Pixels[2,2]; //取棋盘颜色
image1.Canvas.pen.Color:=image1.Canvas.Pixels[2,2];
image1.Canvas.Ellipse(old_qx*21-9,old_qy*20-9+1,old_qx*21+9,old_qy*20+9+1);
old_qx:=-1;old_qy:=-1;
end
else
statusbar1.SimpleText:='对方不同意悔棋';
end
end; //case end
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
old_qx:=-1;//上一次对方棋子位置
old_qy:=-1;
end;
procedure TForm1.Button1Click(Sender: TObject); //重新开局
var i,j:integer;
begin
For i:= 1 To 14 do
For j:= 1 To 14 do
begin
image1.Canvas.Brush.Color:=image1.Canvas.Pixels[2,2]; //取棋盘颜色
image1.Canvas.pen.Color:=image1.Canvas.Pixels[2,2];
image1.Canvas.Ellipse(i*21-9,j*20-9+1,i*21+9,j*20+9+1);
map[i,j]:=0;
end;
// label1.caption:=ColorToString(image1.Canvas.Pixels[2,2]); //TColor;
RadioButton1.Checked:=false;
RadioButton2.Checked:=false;
old_qx:=-1;//上一次对方棋子位置
old_qy:=-1;
statusbar1.SimpleText:='重新开局,选择主机或从机';
label1.Caption:='新局'
end;
procedure TForm1.Button2Click(Sender: TObject); //连接主机
var buf:array[0..20] of char; //1..20 error
str1:string;
begin
NMUDP1.LocalPort:=6666;
NMUDP1.RemotePort:=5555;
NMUDP1.RemoteHost:=edit1.text; //主机的IP
server:=2;
str1:='0'+getip;
StrPCopy(buf,str1);
NMUDP1.SendBuffer(buf,20); // send 本机的IP
GroupBox2.Visible:=false;
// image1.Enabled:=true;
end;
procedure TForm1.Button3Click(Sender: TObject);
var buf:array[1..20] of char;
begin
buf[1]:='4';
NMUDP1.SendBuffer(buf,20);
statusbar1.SimpleText:='你要求悔棋了';
Button3.Enabled:=false;
end;
procedure TForm1.FormActivate(Sender: TObject);
var i:integer;
begin
image1.Canvas.Brush.Color:=$0078DDEF; //棋盘颜色
image1.Canvas.Rectangle(0,0,14*21+20,14*20+20);
for i:=0 to 14 do
begin //横向
image1.Canvas.Brush.Color:=clBLACK; //棋盘颜色
image1.Canvas.pen.Color:=clBLACK;
image1.Canvas.moveto(10,10+20*i);
image1.Canvas.lineto(14*21+10,10+20*i);
image1.Canvas.Brush.Color:=clBLACK; //纵向
image1.Canvas.pen.Color:=clBLACK;
image1.Canvas.moveto(10+21*i,10);
image1.Canvas.lineto(10+21*i,14*20+10);
end;
end;
end.
以上代码在Delphi6+XP通过调试。如果你只有一台电脑,可以把这个项目编译成EXE文件,并且运行两个实例,一个“作为主机”,另一个作为“加入连机棋局”,便可以和自己对弈了。如果检测出输赢,此后就必须“重新开局”进入下一盘。