一邊喝著咖啡一邊玩自己寫的小游戲,一定是很惬意的事情。 今天,就讓我們自己動手編寫一個小小的智力游戲——“獨立鑽 石”,游戲界面如圖1所示。可能大家在玩具店已經玩過這樣的小游 戲了,不過考慮到大多數讀者,我還是先講一下該游戲規則:棋盤 上的棋子可以跳過相鄰的一顆棋子,被跳過的一顆將會被拿掉。棋 子只能以跳躍的方式移動,且只能水平或垂直移動,不能斜跳,也 不能一次跳過兩顆棋子。如此反復,最後若能夠只剩下一顆棋子, 並且棋子位於棋盤中央,那麼你將獲得勝利。
知道了游戲的規則,下面我們就開始編程實現了。我選擇了 Delphi 6來編寫這個游戲,當然別的編程語言實現的方法也比 較相似。
一、游戲的實現思想
首先讓我們來實現棋盤與棋子。如圖2所示,棋盤的背景是 用位圖實現的,我把他們放在了資源文件big.res中了。
我們的棋盤是一個7×7的格子,用二維數組來表示棋盤上 的各個點的狀態就非常自然了。我們定義的數組為:
mymap:array[0..6,0..6] of integer; 這樣,mymap[i,j]的值就可以表示棋盤上該點的狀態。我們規定 1 表示有棋子、0表示無棋子、2 表示不能放棋子,其中i、j 分別表示橫、縱坐標。程序中棋子排放位置的初始值是通過 CHESS.INI 文件定義、由 readblockfromfile和 initnewstate兩個過程讀取的,CHESS.INI文件的片斷內容如下:
[獨立鑽石]
2211122
2111112
1111111
1110111
1111111
2111112
2211122
當棋盤上各個點初始化後 , 調用繪圖過程 FormPaint將棋盤和棋子畫到窗體上就可以顯示出如圖
1 的樣子了,具體的代碼請看後文。 完成棋盤棋子的顯示後,下一步的任務就是實現棋
子的移動了。編程思想是這樣的:當鼠標選中一顆棋子 時,首先定義棋盤上該點不顯示棋子,即該點對應的 mymap分量為0,同時重畫棋盤並將鼠標的光標換成棋 子模樣;接著,當鼠標將棋子落在棋盤上時,再定義落 下那一點的 mymap 分量為 1,而被跳過去的那一點的 mymap 分量為 0,再重畫棋盤就可以實現棋子的移動 了。要把鼠標的光標換為棋子的圖樣,我是這麼寫的: screen.Cursors[1]:=LoadCursor(hinstance,
’curchess’);
//在資源文件中調入光標文件
screen.Cursor:=Tcursor(1);//使屏幕的光標是棋 子圖
注意的是棋子光標是 32 × 32 的。 經過前面的兩步,一個“獨立鑽石”游戲的雛形基
本完成了,下面要做的當然是最重要的算法了。看了前 面的游戲規則,我們可以知道落子的算法可以理解為在 棋子落下時判斷棋子是否是在水平或垂直的方向上移 動,而且是否與原位置之間隔著一個棋子。而判斷游戲 是否結束和輸贏的算法就是對 mymap 數組進行掃描, 如果發現某一點有棋子,即mymap[i,j]=1,那麼就判斷 與該點緊相鄰的那一點是否也有棋子存在,而且在同一 方向上再相鄰的那一點是否沒有棋子的存在,如果同時 滿足的話即可放下一枚棋子。當然對這一點的判斷是要 進行上、下、左、右四個方向的。這部分的算法是在 FormMouseDown 和 FormMouseUp 兩個過程中實現 的,也就是當你在棋盤上松開鼠標落下棋子時判斷。 下面就是我所作的“獨立鑽石”的源代碼,窗體上
面只要放上一個菜單就可以了。
二、游戲的源代碼
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Menus, ExtCtrls;
type
TForm1 = class(TForm)
PopupMenu1: TPopupMenu;
N3: TMenuItem;
popmenuexit: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure popmenuexitClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
procedure readblockfromfile(fname,sname:string;ts:tstrings);
procedure initnewstate(statename:string);
procedure popmenuClick(Sender: TObject);
{ Private declarations }
public
mymap:array[0..6,0..6] of integer;
{ Public declarations }
end;
var
Form1: TForm1;
OldMyX,OldMyY:integer; //定義全局變量棋子位置發生變化前的坐標
isup:boolean=false; // 定義判斷是否處於棋子移動過程的變量
iswin:boolean=false; // 定義判斷游戲是否結束的變量
chessmanuals:TStringList; // 定義棋盤的名稱
curManuals:integer; //當前棋譜
implementation
{$R *.dfm}
{$R big.res}// 定義自定義的資源文件,其中包括棋盤的背景圖 BitBack,棋子 的圖BitChess、提起棋子的鼠標光標CurChess、不提起棋子的鼠標光標CurHand
// 讀取 INI 配置文件的過程
procedure Tform1.readblockfromfile(fname,sname:string; ts:TStrings);
var
i:integer;
tsfile:TStringList;
fromidx:integer;
s:string;
begin
ts.Clear;
if FileExists(fname) then begin
tsfile:=TStringList.Create;
tsfile.LoadFromFile(fname);
fromidx:=tsfile.IndexOf(trim(sname));
if fromidx<0 then
fromidx:=tsfile.count;
for i:=fromidx+1 to fromidx+7 do begin
s:=trim(tsfile.Strings[i]);
ts.Append(s);
end;
tsfile.Free;
end
else
ts.Clear;
end;
// 初始化棋盤棋子排列的過程
procedure TForm1.initnewstate(statename:string);
var
i,j:integer;
tslevel:TStringList;
begin
tslevel:=TStringList.Create;
form1.readblockfromfile(’chess.ini’,statename, tslevel); // 讀取同目錄下的 CHESS.INI 文件
for i:=0 to 6 do begin
for j:=1 to 7 do begin
mymap[i,j-1]:=strtoint(tslevel.Strings[i][j]);
// 定義二維數組中各元素的值,對應請棋盤上個點的狀態
end;
end;
tslevel.Free;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
tsfile:TStrings;
title:string;
i:integer;
menuitem:TMenuItem;
begin
form1.Width:=265;
form1.Height:=265;
// 載入屏幕光標,定義初始屏幕光標
screen.Cursors[1]:=LoadCursor(hinstance,’curchess’);
screen.Cursors[2]:=LoadCursor(hinstance,’curhand’);
screen.Cursor:=TCursor(2);
chessmanuals:=TStringList.Create;
chessmanuals.Clear;
tsfile:=TStringList.Create;
tsfile.LoadFromFile(’chess.ini’);
for i:=0 to tsfile.Count-1 do begin
title:=tsfile.Strings[i];
if (title[1]=’[’) then begin
menuitem:=TMenuItem.Create(PopupMenu1);
menuitem.Caption:=title;
menuitem.Tag:=chessmanuals.Count;
menuitem.OnClick:=popmenuClick;
PopupMenu1.Items.Insert(chessmanuals.Count,menuitem);
chessmanuals.Add(title);
end;
end;
curManuals:=0;
initnewstate(chessmanuals.Strings[curManuals]);
FormPaint(self);
end;
// 窗口的自畫過程
procedure TForm1.FormPaint(Sender: TObject);
var i,j:integer;
mybitmap,bufbitmap:Tbitmap;
begin
myBitmap:=TBitmap.Create;
bufbitmap:=TBitmap.Create;
bufbitmap.Height:=265;
bufbitmap.Width:=265;
mybitmap.LoadFromResourceName(hInstance, ’bitback’);
// 對窗口背景重畫棋盤
bufbitmap.Canvas.CopyRect(rect(0,0,265,265),mybitmap.Canvas,rect(0,0,265,
265));
mybitmap.LoadFromResourceName(hInstance, ’bitchess’);
mybitmap.TransparentColor:=clwhite;
mybitmap.Transparent:=true;
for i:=0 to 6 do begin
for j:=0 to 6 do begin
if mymap[i,j]=1 then
bufbitmap.Canvas.Draw(i*34+12,j*34+12,mybitmap);
end;
end;
// 對棋盤上的棋子進行重畫
form1.Canvas.CopyRect(rect(0,0,265,265),bufbitmap.Canvas,rect(0,0,265,265));
myBitmap.free;
bufbitmap.Free;
end;
// 鼠標點下的過程,在這個地方可以理解為鼠標左鍵選中棋子的過程 procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
myx,myy:integer;// 棋盤坐標
begin
if button=mbleft then begin
// 因為棋盤中每格的大小為 34,就根據鼠標在窗口中的相對坐標,來換算出 棋盤坐標
myx:=x div 34;
myy:=y div 34;
oldmyx:=myx;
oldmyy:=myy;
if (mymap[myx,myy]=1) and (isup=false) then begin
// 判斷是否點下的地方有棋子且鼠標還處於沒有提起棋子的狀態
screen.Cursor:=Tcursor(1);
mymap[myx,myy]:=0;// 棋盤上的棋子被提走
isup:=true; //鼠標已經處於提起棋子的狀態
end;
FormPaint(self);
end;
end;
// 鼠標松開的過程,在這個地方可以理解為鼠標左鍵將棋子放在棋盤上的過程 procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
myx,myy:integer;
ii,i,j:integer;
begin
if button=mbLeft then begin
screen.Cursor:=Tcursor(2);//落下棋子後鼠標的光標恢復原樣
myx:=x div 34;
myy:=y div 34;
//這是在垂直向下的方向上進行棋子的移動。要判斷新坐標是與原坐標相隔 一個棋子且新的坐標上沒有棋子存在,那麼新作標點上回落下棋子,原坐標點上 和跳過的點上的棋子消失
if (myx=oldmyx+2) and (myy=oldmyy) and (mymap[myx,myy]=0)
and (mymap[myx-1,myy]=1) then begin
mymap[myx,myy]:=1;
mymap[myx-1,myy]:=0;
isup:=false;
end;
// 這是在垂直向上的方向上進行棋子的移動
if (myx=oldmyx-2) and (myy=oldmyy) and (mymap[myx,myy]=0)
and (mymap[myx+1,myy]=1) then begin
mymap[myx,myy]:=1;
mymap[myx+1,myy]:=0;
isup:=false;
end;
// 這是在水平向右的方向上進行棋子的移動
if (myx=oldmyx) and (myy=oldmyy+2) and (mymap[myx,myy]=0)
and (mymap[myx,myy-1]=1) then begin
mymap[myx,myy]:=1;
mymap[myx,myy-1]:=0;
isup:=false;
end;
// 這是在水平向左的方向上進行棋子的移動
if (myx=oldmyx) and (myy=oldmyy-2) and (mymap[myx,myy]=0)
and (mymap[myx,myy+1]=1) then begin
mymap[myx,myy]:=1;
mymap[myx,myy+1]:=0;
isup:=false;
end;
// 判斷是否在原位置上落下棋子
if (isup=true) and (mymap[oldmyx,oldmyy] <>2) then begin
mymap[oldmyx,oldmyy]:=1;
isup:=false;
end;
FormPaint(self);
iswin:=true;
ii:=0;
// 下面是對整個棋盤進行掃描,看還剩下多少顆棋子
for i:=0 to 6 do begin
for j:=0 to 6 do begin
//如果發現某一點有棋子,那麼就判斷與該點緊相鄰的那一點是否也有 棋子存在,而且在同一方向上再相鄰的那一點是否沒有棋子的存在也就是可以落 下一枚棋子,說明了還能夠繼續游戲
if (mymap[i,j]=1) and (mymap[i+1,j]=1) and (mymap[i+2,j]=0) then iswin:=false;
if (mymap[i,j]=1) and (mymap[i-1,j]=1) and (mymap[i-2,j]=0) then iswin:=false;
if (mymap[i,j]=1) and (mymap[i,j+1]=1) and (mymap[i,j+2]=0) then iswin:=false;
if (mymap[i,j]=1) and (mymap[i,j-1]=1) and (mymap[i,j-2]=0) then iswin:=false;
if mymap[i,j]=1 then ii:=ii+1; //統計還剩幾顆
end;
end;
// 如果還剩下中間的那一顆,就是勝利
if (iswin) then
if (ii=1) and (mymap[3,3]=1) then
showmessage(’ 恭喜你,成功了! ’)
else
begin
showmessage(’ 還剩下 ’+INTTOSTR(ii)+’ 顆! 繼續努力吧! ’);
initnewstate(chessmanuals.Strings[curManuals]);
FormPaint(self);
end;
end;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
myx,myy:integer;
begin
myx:=x div 34;
myy:=y div 34;
//如果鼠標的鼠標左鍵點下且點的是不能有棋子的地方,移動鼠標時窗口跟著 一起移動
if (ssleft in shift) and (mymap[myx,myy]=2) and (isup=false) then begin
releasecapture;
form1.perform(WM_syscommand, $F012, 0);
end;
end;
procedure TForm1.popmenuClick(Sender: TObject);
begin
with Sender as TMenuItem do
begin
curManuals:=tag;
initnewstate(chessmanuals.Strings[tag]);
FormPaint(self);
end;
end;
procedure TForm1.popmenuexitClick(Sender: TObject);
begin
close;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
chessmanuals.Free;
end;
end.
到這裡,我們漂亮的“獨立鑽石”游戲已經做好了,潇 灑地按下F9就可以見到你的傑作了。怎麼樣?還不錯吧,這 個游戲的難度可是很高的哦!