汉诺塔问题是一个经典的递归问题,涉及到将一组盘子从一个柱子移动到另一个柱子,同时满足特定的规则。本文将介绍一个使用C#和Windows Forms开发的汉诺塔游戏解决方案。
该应用程序需要满足以下要求:
为了实现清晰的界面与后端分离,设计了以下几个类:
MoveCalculator类返回一个Move类列表,GameState类则根据这些移动进行游戏。
GameForm包含所有图形组件,并作为应用程序的驱动器。使用了PictureBox控件作为盘子和柱子的基础对象,但需要更多的功能,因此扩展了PictureBox控件。Pole PictureBox需要跟踪其上的盘子,通过维护一个排序的盘子列表。Disk PictureBox负责移动自身。还添加了拖放功能,使游戏更加有趣。
用于解决谜题的算法是一个简单的递归函数。在函数中,每个移动都被添加到一个列表中,这个列表用于解决谜题。游戏的初始状态是所有盘子都在“起始柱子”上。执行列表中的所有移动后,所有盘子应该都在“结束柱子”上。
以下是应用程序中的一些代码片段:
MoveCalculator类接受用户想要玩的盘子数量,并返回解决谜题所需的移动列表。
public static class MoveCalculator {
    private static void Calculate(int n, int fromPole, int toPole) {
        if (n == -1) {
            return;
        }
        int intermediatePole = GetIntermediatePole(fromPole, toPole);
        Calculate(n - 1, fromPole, intermediatePole);
        moves.Add(new Move(fromPole, toPole));
        Calculate(n - 1, intermediatePole, toPole);
    }    
}
    
Move类包含FromPole和ToPole。由于总是移动FromPole顶部的盘子,所以这两个柱子就足够了。Move还包含有关自身的信息,如AffectCount和IsValid。
public class Move {
    public Pole FromPole {
        get;
        set;
    }
    public Pole ToPole {
        get;
        set;
    }
    public bool AffectCount() {
        if (ToPole.Equals(FromPole)) {
            return false;
        }
        return IsValid();
    }
    public bool IsValid() {
        if (ToPole.Equals(FromPole)) {
            return true;
        }
        return ToPole.AllowDisk(FromPole.getTopDisk());
    }    
}
    
UI使用两个自定义PictureBox控件:Pole和Disk。Disk可以从一个Pole移动到另一个Pole。Pole包含一个Disks列表。
public class Disk : PictureBox {
    public int Number {
        get;
        set;
    }
    public Disk(int Number) : base() { ... }
    public void MoveToPole(Pole DestinationPole) { ... } 
}
public class Pole : PictureBox {
    public SortedList Disks {
        get;
        set;
    }
    public int Number {
        get;
        set;
    }
    public Pole(int number) { 
        ...    
    }
    public bool IsEmpty() {
        return Disks.Count == 0;
    }
    public bool AllowDisk(Disk disk) {
        if (disk == null) {
            return false;
        }
        if (Disks.Count == 0) {
            return true;
        }
        return getTopDisk().Number > disk.Number;
    }
    public Disk getTopDisk() {
        if (Disks.Count > 0) {
            return Disks.First().Value;
        }
        return null;
    }
    public void RemoveDisk() {
        Disks.Remove(Disks.First().Key);
    }
    public void AddDisk(Disk disk) {
        if (AllowDisk(disk)) {
            disk.MoveToPole(this);
            Disks.Add(disk.Number, disk);
        }
    }
}
     
GameState类用于存储游戏状态信息。GameState应该开始游戏,执行移动并检查是否完成。
public static class GameState {
    public static List Poles = new List();
    public static List ImageList = new List();
    public static int MoveCount {
        get;
        set;
    }
    public static int NumberOfDisks {
        get;
        set;
    }
    static GameState() {
        LoadImagesFromFile();
        RestartGame(3);
    }
    public static int Move(Move move) {
        if (move.AffectCount()) {
            MoveCount++;
        }
        if (move.IsValid()) {
            Disk disk = move.FromPole.getTopDisk();
            Poles[move.FromPole.Number].RemoveDisk();
            Poles[move.ToPole.Number].AddDisk(disk);
            return MoveCount;
        }
        else {
            return -1;
        }  
    }
    public static bool IsSolved() {
        return (Poles[Properties.Settings.Default.EndPole].Disks.Count == NumberOfDisks);
    } 
}
        
[TestClass()]
public class MoveCalculatorTest {
    ...
    [TestMethod()]
    public void GetMoveCountTest() {
        int actualMoveCount = MoveCalculator.GetMoveCount(3);
        int expectedMoveCount = 7;
        Assert.AreEqual(expectedMoveCount, actualMoveCount);
        actualMoveCount = MoveCalculator.GetMoveCount(4);
        expectedMoveCount = 15;
        Assert.AreEqual(expectedMoveCount, actualMoveCount);
        actualMoveCount = MoveCalculator.GetMoveCount(5);
        expectedMoveCount = 31;
        Assert.AreEqual(expectedMoveCount, actualMoveCount);
    }  
}
[TestClass()]
public class GameStateTest  {
...
    [TestMethod()]
    public void IsSolvedTest() {
        GameState.RestartGame(numberOfDisks);
        bool expectedBefore = false;
        bool actualBefore = GameState.IsSolved();
        solveGame();
        bool expectedAfter = true;
        bool actualAfter = GameState.IsSolved();
        Assert.AreEqual(expectedBefore, actualBefore);
        Assert.AreEqual(expectedAfter, actualAfter);
    }
}