《GOF设计模式》—创建型模式—Delphi源码示例:未基于模式的迷宫

示例:简单的迷宫

实现:

我们将举一个通用的例子—为一个电脑游戏创建一个迷宫—来说明它们的实现。这个迷宫和游戏将随着各种模式不同而略有区别。有时这个游戏将仅仅是找到一个迷宫的出口;在这种情况下,游戏者可能仅能见到该迷宫的局部。有时迷宫包括一些要解决的问题和要战胜的危险,并且这些游戏可能会提供已经被探索过的那部分迷宫地图。

我们将忽略许多迷宫中的细节以及一个迷宫游戏中有一个还是多个游戏者。我们仅关注迷宫是怎样被创建的。我们将一个迷宫定义为一系列房间,一个房间知道它的邻居;可能的邻居要么是另一个房间,要么是一堵墙、或者是到另一个房间的一扇门。

RoomDoorWall定义了我们所有的例子中使用到的构件。我们仅定义这些类中对创建一个迷宫起重要作用的一些部分。我们将忽略游戏者、显示操作和在迷宫中四处移动操作,以及其他一些重要的却与创建迷宫无关的功能。

每一个房间有四面,我们使用枚举类型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.

 

 

作者写点废话哈: 1、先是看到手机上有个小游戏,填字游戏,横竖相连,像个迷宫 2、就用Delphi 做了个由 panel 数组 组成的迷宫,墙都是方块,丑死了。 3、再查查网上有不少迷宫样式,其中有的迷宫是单墙的,而且任意两处都是想通的。 4、再做了个四面墙都可打通的迷宫,甚至做了个斜线通道的。 5、觉得三角形迷宫更有挑战性,另外想试试以前学的数据结构指针、链表、树、连通图的知识是不是忘光了, 就做了个三角形迷宫,而且索性做成一个完善的 Delphi 控件,而且有不少属性。。。 6、有几个要解释下:1)迷宫是个连通图,每个正三角形与三个倒三角形相连,每个倒三角形与三个正三角形相连; 2)采用递归,从一个节点开始构造整个连通图;3)查找、遍历连通图时用外部二维数组标识来防止重复; 3)构造迷宫采用的是所谓 随机prim 算法;4)迷宫的宽与三角形边长、列数相互制约,迷宫的高由三角形的高 (正三角形的高通过边长计算的)与行数决定;5)使用指针时最容易丢掉 ^ 这个符号,例如某节点是 Pmm 指针型, 对其属性的引用就不能用 Pmm.Value 而必须用 Pmm^.Value ,机器编译时不会提示错误,但运行时老是出错! 6)绘图通过计算三角形的顶点坐标来构造;7)控件父类是 TGraphicControl ,试了好几种最后它最好, 要覆盖 paint 方法绘图;8)构造类时如 FGridWidth 的内部数据与属性 GridWidth 不要混淆使用, 记住 内部数据赋值,属性引用,属性(Published)是给运行期或者设计面板上别人使用的,内部数据是封装的; 9)发布自定义控件前先准备一个包含 类 一样名称的 ICO 的 DCR 文件,好像只有 Delphi7 里的 Image Editor 可以制作,这个工具包括两个文件 ImageEdit.exe 和 ImageD32.dll 两个文件 7、好像前后陆续花了近两个星期吧,终于基本完善了。该学点其他东西了 -- by chenxz
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值