模型示意图:
需求分析
- 1、实现汉诺塔每层移动的动画效果
- 2、实现通过用户指定层数来进行相应层数的移动效果
总体设计
- 1、模型基于EasyX图形库实现可视化效果
- 2、编程语言采用C语言
- 3、采用栈这种数据结构的思想来实现层的迁移(例如:将A塔的最顶层移动到B塔的最顶层)
详细设计
1、可视化界面设计:
- 大小
- 宽度:500px
- 高度:600px
- 代码:
#include <graphics.h> // 导入EasyX图形库头文件
#include <stdio.h>
// main()
int main() {
initgraph(500, 600); // 初始化一个500*600的窗口
cleardevice(); // 刷新窗口
closegraph(); // 关闭窗口
return 0;
}
2、变量设计
- 这里我采用了面象对象程序的设计思想,将塔和层都是抽象为结构体,这样设计的好处不仅可以对程序中诸多变量的管理,也能更好的采用栈的思想来设计程序。
- 层的设计:
- 位置:
- 横坐标:x
- 纵坐标:y
- 大小:
- 宽度:width
- 高度:height
- 颜色
- RGB:color_rgb[3];
- 指向下一个结点的指针:
- 指针:next
- 代码:
- 位置:
typedef struct PLATE {
int x;
int y;
int width;
int height;
int color_rgb[3];
struct PLATE* next;
}*plate;
- 塔的设计:
- 位置:
- 横坐标:x
- 纵坐标:y
- 栈顶指针:top(因为采用栈的思想所以我将每座塔设计为栈的结构)
- 代码:
- 位置:
// 塔
typedef struct PILLAR {
int x;
int y;
struct PLATE* top;
}*pillar;
- 计步器:
- step
- 代码:
//步子统计
int step = 0;
3、函数设计
- main函数的设计
- 窗口的创建,三座塔的初始化,以及陈序的主要流程
- 代码:
// main()
int main() {
initgraph(500, 600); //初始化一个500*600的窗口
BeginBatchDraw();
cleardevice(); // 刷新屏幕
srand((unsigned)time(NULL));
//定义三座塔
pillar A = newPillar(100, 400);
pillar B = newPillar(250, 400);
pillar C = newPillar(400, 400);
//定义层数
int n = 6;
batchAdd(A, n);
draw(A, B, C, n);
char ch = _getch();
while (ch == ' ') {
move(n, A, B, C); // 移动层
ch = _getch();
A->top = NULL;
B->top = NULL;
C->top = NULL;
step = 0;
batchAdd(A, n); // 将层全部按小层在大层上面的顺序放入A塔
}
ch = _getch();
EndBatchDraw();
closegraph();
return 0;
}
- 层的初始化:
- 采用面向对象设计思想,将函数设计为层对象的构造器
- 代码:
//初始化层
plate newPlate(int x, int y, int width, int height, int R, int G, int B) {
plate p = (plate)malloc(sizeof(struct PLATE));
if (p == NULL) {
printf("创建层失败!\n");
exit(NULL);
}
p->x = x;
p->y = y;
p->width = width;
p->height = height;
p->color_rgb[0] = R;
p->color_rgb[1] = G;
p->color_rgb[2] = B;
p->next = NULL;
return p;
}
- 塔的初始化
- 设计思想同层
- 代码:
//初始化塔
pillar newPillar(int x, int y) {
pillar p = (pillar)malloc(sizeof(pillar));
if (p == NULL) {
printf("创建塔失败!\n");
exit(NULL);
return NULL;
}
p->x = x;
p->y = y;
p->top = NULL;
return p;
}
- 入栈操作
- 传入塔和层,将层放入塔顶(栈顶)
- 代码
//入栈
void add(pillar &pil, plate pla) {
if (pla == NULL) {
printf("盘子不存在!\n");
return;
}
pla->next = pil->top; //将入栈元素的next指针指向栈顶元素
pil->top = pla; // 令入栈元素成为栈顶元素
}
- 出栈操作
- 传入塔,移除塔顶(栈顶元素)
- 代码:
//出栈
plate pop(pillar &pil) {
if (pil->top == NULL)
{
printf("该柱子为空柱!\n");
exit(NULL);
return NULL;
}
plate p = pil->top; // 记录栈顶元素(要出栈的元素)位置
pil->top = pil->top->next; // 将栈顶指针移动后一个元素
return p; // 将出栈元素返回
}
- 批量初始化层并放入指定塔内
- 传入塔及层数,依次从大到小初始化层并放入塔中
- 代码:
// 批量生成层并将它们放入塔中
void batchAdd(pillar &pil, int n) {
for (int i = 0; i < n; i++) {
add( // 将层放入塔中
pil, // 要放入的塔
newPlate( // 通过层数,安从大到小的顺序以及塔的位置计算出每层的大小和位置来初始化每层
pil->x - (n - i) * 20 / 2,
pil->y - (i + 1) * 20,
(n - i) * 20,
20,
// 随机生成每层的颜色
rand() % 255,
rand() % 255,
rand() % 255
)
);
}
}
- 绘制层
- 传入塔,以便绘制其中的每层
- 代码:
// 绘制层
void drawPlate(pillar &pil) {
if (pil->top == NULL) {
exit(NULL);
}
plate p = pil->top;
while (p != NULL) { // 遍历栈
setfillcolor(RGB(p->color_rgb[0], p->color_rgb[1], p->color_rgb[2]));
fillrectangle(p->x, p->y, p->x + p->width, p->y + p->height);
p = p->next;
}
}
- 整体绘制
- 绘制包括层在内的塔、层数和步数的绘制
- 代码:
// 绘制
void draw(pillar &A, pillar &B, pillar &C, int p_n) {
int n = 4;
wchar_t str[2];
//画ABC塔
line(A->x, A->y, A->x, A->y - n * 50);
line(A->x - n * 35 / 2, A->y, A->x + n * 35 / 2, A->y);
_stprintf(str, _T("A"));
outtextxy(90, 410, str);
line(B->x, B->y, B->x, B->y - n * 50);
line(B->x - n * 35 / 2, B->y, B->x + n * 35 / 2, B->y);
_stprintf(str, _T("B"));
outtextxy(240, 410, str);
line(C->x, C->y, C->x, C->y - n * 50);
line(C->x - n * 35 / 2, C->y, C->x + n * 35 / 2, C->y);
_stprintf(str, _T("C"));
outtextxy(390,410, str);
//画盘子
if (A->top != NULL) {
drawPlate(A);
}
if (B->top != NULL) {
drawPlate(B);
}
if (C->top != NULL) {
drawPlate(C);
}
// 数据可视化
wchar_t str_n[20];
wchar_t str_step[20];
_stprintf(str_n, _T("层数:%d"), p_n);
_stprintf(str_step, _T("步数:%d"), step);
settextcolor(RGB(255, 255, 255));
settextstyle(15, 15, _T("宋体"));
outtextxy(10, 10, str_n);
outtextxy(10, 30, str_step);
}
- 移动动画
- 将三座塔以及层数传入,实现将某个塔的最顶层移动另一个塔的最顶层
- 代码:
// 移动动画
void MoveAnimation(pillar &A, pillar &B, pillar &C, int p_n) {
int n = 4;
int temp;
if (B->top != NULL) {
temp = B->top->y - B->top->height;
}
else {
temp = B->y - A->top->height;
}
// 循环判断移出塔的塔顶层的位置直到该层到达移入塔的塔顶为止
while (A->top->x != B->x - A->top->width / 2 || A->top->y != temp) {
// 判断是否移出
if (A->top->y >= A->y - n * 50 - 10 && A->top->x == A->x - A->top->width / 2) {
A->top->y--;
}
// 判断是否移到移入塔的上方
else if (A->top->x != B->x - A->top->width / 2) {
// 判读是左移还是右移
if (A->top->x < B->x - A->top->width / 2) {
A->top->x++;
}
else {
A->top->x--;
}
}
// 判断是否到达移入塔的塔顶位置
else if (A->top->y < temp) {
A->top->y++;
}
draw(A, B, C, p_n);
FlushBatchDraw();
//Sleep(10);
cleardevice();
}
add(B, pop(A)); //将A塔中最上面的一层移到B塔中
step++;
}
- 核心函数
- 汉诺塔的递归算法
- 代码
// 移动
void move(int n, pillar &A, pillar &B, pillar &C) {
static int t = n;
if (n == 0) return;
move(n - 1, A, C, B);
MoveAnimation(A, C, B, t);
draw(A, B, C, t);
move(n - 1, B, A, C);
}