示例:简单的迷宫
实现:
我们将举一个通用的例子—为一个电脑游戏创建一个迷宫—来说明它们的实现。这个迷宫和游戏将随着各种模式不同而略有区别。有时这个游戏将仅仅是找到一个迷宫的出口;在这种情况下,游戏者可能仅能见到该迷宫的局部。有时迷宫包括一些要解决的问题和要战胜的危险,并且这些游戏可能会提供已经被探索过的那部分迷宫地图。
我们将忽略许多迷宫中的细节以及一个迷宫游戏中有一个还是多个游戏者。我们仅关注迷宫是怎样被创建的。我们将一个迷宫定义为一系列房间,一个房间知道它的邻居;可能的邻居要么是另一个房间,要么是一堵墙、或者是到另一个房间的一扇门。
类Room、Door和Wall定义了我们所有的例子中使用到的构件。我们仅定义这些类中对创建一个迷宫起重要作用的一些部分。我们将忽略游戏者、显示操作和在迷宫中四处移动操作,以及其他一些重要的却与创建迷宫无关的功能。
每一个房间有四面,我们使用枚举类型Direction来指定房间的东南西北。
类MapSite是所有迷宫组件的公共抽象类。为简化例子,MapSite仅定义了一个操作Enter,它的含义决定于你在进入什么。如果你进入一个房间,那么你的位置会发生改变。如果你试图进入一扇门,那么这两件事中就有一件会发生:如果门是开着的,你进入另一个房间。如果门是关着的,那么你就会碰壁。Enter为更加复杂的游戏操作提供了一个简单基础。例如,如果你在一个房间中说“向东走”,游戏只能确定直接在东边的是哪一个MapSite并对它调用Enter。特定子类的Enter操作将计算出你的位置是发生改变,还是你会碰壁。在一个真正的游戏中,Enter可以将移动的游戏者对象作为一个参数。
Room类是MapSite的一个具体的子类,而MapSite定义了迷宫中构件之间的主要关系。Room有指向其他MapSite对象的引用,并保存一个房间号,这个数字用来标识迷宫中的房间。
Wall类、Door类描述了一个房间的每一面所出现的墙壁或门。
我们不仅需要知道迷宫的各部分,还要定义一个用来表示房间集合的Maze类。用RoomNo操作和给定的房间号,Maze就可以找到一个特定的房间。RoomNo可以使用线形搜索、hash表、甚至一个简单数组进行一次查找。但我们在此处并不考虑这些细节,而是将注意力集中于如何指定一个迷宫对象的构件上。
我们定义的另一个类是MazeGame,由它来创建迷宫。一个简单直接的创建迷宫的方法是使用一系列操作将构件增加到迷宫中,然后连接它们。例如,MazeGame的成员函数CreatMaze将创建一个迷宫,这个迷宫由两个房间和它们之间的一扇门组成。
代码:
unit uBaseMaze;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls;
type
{房间的四个方向}
TDirection = (North = 0,South = 1,East = 2,West = 3);
const
DirectionNames: array[TDirection] of string = ('北', '南', '东', '西');
type
{迷宫构件}
TMapSite = class
private
FStateMsg: string;
public
function Enter: Boolean; virtual; abstract;
//---
property StateMsg: string read FStateMsg write FStateMsg;
end;
{房间}
TRoom = class(TMapSite)
private
FSides: array[TDirection] of TMapSite;
FRoomNumber: Integer;
protected
function GetSides(Direction: TDirection): TMapSite;
procedure SetSides(Direction: TDirection; const Value: TMapSite);
public
constructor Create(ARoomNumber: integer);
destructor Destroy; override;
//---
function Enter: Boolean; override;
//---
property RoomNumber: Integer read FRoomNumber;
property Sides[Direction: TDirection]: TMapSite read GetSides write SetSides;
end;
{墙壁}
TWall = class(TMapSite)
public
function Enter: Boolean; override;
end;
{门}
TDoor = class(TMapSite)
private
FRoom1,FRoom2: TRoom;
//--门是否开启
FIsOpen: Boolean;
procedure Initialize(room1,room2: TRoom);
public
constructor Create(room1,room2: TRoom); virtual;
destructor Destroy; override;
//---
function Enter: Boolean; override;
{从一个房间(传入参数)进入另一个房间(输出结果)}
function OtherSideFrom(Room: TRoom): TRoom;
end;
TRoomList = class
private
FItemList: TList;
function GetCount: Integer;
function GetItems(Index: integer): TRoom;
protected
procedure Clear;
public
constructor Create;
destructor Destroy; override;
//---
function Add(const Room: TRoom): integer;
//---
property Count: Integer read GetCount;
property Items[Index: integer]: TRoom read GetItems;
end;
{迷宫组件}
TMaze = class
private
FRooms: TRoomList;
public
constructor Create;
destructor Destroy; override;
//---
{在迷宫中加入一个房间}
procedure AddRoom(Room: TRoom);
{根据房间编号取得房间}
function RoomNo(RoomNumber: Integer): TRoom;
end;
{迷宫游戏}
TMazeGame = class
public
function CreateMaze: TMaze;
end;
implementation
constructor TRoom.Create(ARoomNumber: integer);
//---
procedure _InitSides;
var
Direction: TDirection;
begin
for Direction := Low(FSides) to High(FSides) do
FSides[Direction] := nil;
end;
begin
inherited Create;
//---
FRoomNumber := ARoomNumber;
//---
_InitSides;
end;
destructor TRoom.Destroy;
//---
procedure _ClearSides;
var
Direction: TDirection;
begin
for Direction := Low(FSides) to High(FSides) do
begin
if FSides[Direction] <> nil then
FSides[Direction].Free;
end;
end;
begin
_ClearSides;
//---
inherited;
end;
function TRoom.Enter: Boolean;
begin
self.StateMsg := format('进入房间%d', [FRoomNumber]);
Result := true;
end;
function TRoom.GetSides(Direction: TDirection): TMapSite;
begin
Result := FSides[Direction];
end;
procedure TRoom.SetSides(Direction: TDirection; const Value: TMapSite);
begin
FSides[Direction] := Value;
end;
function TWall.Enter: Boolean;
begin
self.StateMsg := '碰到墙';
Result := false;
end;
constructor TDoor.Create;
begin
inherited Create;
//---
Initialize(room1,room2);
end;
destructor TDoor.Destroy;
//---
procedure _ClearDoor(Room: TRoom);
var
Direction: TDirection;
begin
if Room <> nil then
begin
with Room do
begin
for Direction := Low(TDirection) to High(TDirection) do
begin
if Sides[Direction] = self then
begin
Sides[Direction] := nil;
exit;
end;
end;
end;
end;
end;
begin
_ClearDoor(FRoom1);
_ClearDoor(FRoom2);
//---
inherited;
end;
function TDoor.Enter: Boolean;
begin
self.StateMsg := '碰到门';
Result := true;
end;
procedure TDoor.Initialize(room1,room2: TRoom);
begin
FRoom1 := room1;
FRoom2 := room2;
FIsOpen := False;
end;
function TDoor.OtherSideFrom(Room: TRoom): Troom;
begin
if Room = FRoom1 then
Result := FRoom2
else
Result := FRoom1;
end;
constructor TMaze.Create;
begin
inherited;
//---
FRooms := TRoomList.Create;
end;
destructor TMaze.Destroy;
begin
FRooms.Free;
//---
inherited;
end;
procedure TMaze.AddRoom(Room: TRoom);
begin
FRooms.Add(Room);
end;
function TMaze.RoomNo(RoomNumber: Integer): TRoom;
var
i: Integer;
begin
Result := nil;
//---
with FRooms do
begin
for i := 0 to Count - 1 do
begin
if Items[i].Roomnumber = RoomNumber then
begin
Result := Items[i];
Exit;
end;
end;
end;
end;
function TMazeGame.CreateMaze: TMaze;
var
aMaze: TMaze;
r1,r2: Troom;
theDoor: TDoor;
begin
//---建构一个maze,有两个Room,一个Door,六面Wall
aMaze := TMaze.Create;
//---
r1 := TRoom.Create(1);
r2 := TRoom.Create(2);
//---
theDoor := TDoor.Create(r1,r2);
//---
aMaze.AddRoom(r1);
aMaze.AddRoom(r2);
//---
r1.SetSides(North,TWall.Create);
r1.SetSides(East,theDoor);
r1.SetSides(South,TWall.Create);
r1.SetSides(West,TWall.Create);
//---
r2.SetSides(North,TWall.Create);
r2.SetSides(East,TWall.Create);
r2.SetSides(South,TWall.Create);
r2.SetSides(West,theDoor);
//---
result := aMaze;
end;
constructor TRoomList.Create;
begin
inherited;
//---
FItemList := TList.Create;
end;
destructor TRoomList.Destroy;
begin
Clear;
FItemList.Free;
//---
inherited;
end;
function TRoomList.Add(const Room: TRoom): integer;
begin
if Assigned(Room) then
Result := FItemList.Add(Room)
else
Result := -1;
end;
procedure TRoomList.Clear;
var
i: Integer;
begin
with FItemList do
begin
for i := 0 to Count - 1 do
TObject(Items[i]).Free;
//---
Clear;
end;
end;
function TRoomList.GetCount: Integer;
begin
Result := FItemList.Count;
end;
function TRoomList.GetItems(Index: integer): TRoom;
begin
Result := FItemList[Index];
end;
end.
unit Unit1;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,
Dialogs,StdCtrls,Menus,uBaseMaze;
type
TForm1 = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure ListBox1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
FMazeGame: TMazeGame;
FMaze: TMaze;
FCurRoom: TRoom;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
self.KeyPreview := true;
//---
FMazeGame := TMazeGame.Create;
FMaze := FMazeGame.CreateMaze;
//---
FCurRoom := FMaze.RoomNo(1);
with FCurRoom do
begin
Enter;
ListBox1.Items.Add(StateMsg);
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FMaze.Free;
FMazeGame.Free;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift:
TShiftState);
//---
procedure _EnterRoomSide(Direction: TDirection);
var
ARoom: TRoom;
begin
with FCurRoom do
begin
if Sides[Direction] <> nil then
begin
with Sides[Direction] do
begin
if Enter then
begin
ListBox1.Items.Add(DirectionNames[Direction] + ':' + StateMsg);
//---
if Sides[Direction] is TDoor then
begin
ARoom := TDoor(Sides[Direction]).OtherSideFrom(FCurRoom);
if ARoom <> nil then
begin
if ARoom.Enter then
FCurRoom := ARoom;
ListBox1.Items.Add(ARoom.StateMsg);
end;
end;
end
else
ListBox1.Items.Add(DirectionNames[Direction] + ':' + StateMsg);
end;
end;
end;
end;
begin
case Ord(Key) of
VK_LEFT: _EnterRoomSide(East);
VK_RIGHT: _EnterRoomSide(West);
VK_UP: _EnterRoomSide(South);
VK_DOWN: _EnterRoomSide(North);
end;
end;
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word; Shift:
TShiftState);
begin
Key := 0;
end;
end.