语言大作业——推箱子小游戏(使用DevC++编写,控制台版本)

C语言大作业——推箱子小游戏(使用DevC++编写,控制台版本)



前言

学完了C语言,是时候该应用了!这个小项目包含了C语言数据类型、循环分支、结构体、指针以及文件读写内容,也有简单的排序算法等,通过这个项目可以很好的巩固C语言的基础知识,代码行数562行也满足一般C语言大作业的要求,同时写一个属于自己的小游戏也是一个有趣的过程。该小游戏在逻辑上还有很多地方需要优化,剩下的就交给大家了。


效果展示

开始菜单

在这里插入图片描述

关卡展示

在这里插入图片描述
在这里插入图片描述

排行榜

在这里插入图片描述

文件关系

在这里插入图片描述

注意:num文件中必须事先存入1才能正常运行(即所有num文件中不能为空)


代码部分

/*
 * @LastEditors: Fu Zhibin
 * @LastEditTime: 2024-07-14
 * @Description: 推箱子小游戏,包含开始菜单,移动推箱子,排行榜等功能 
*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h> // 用于支持 getch 函数
#include <Windows.h>
#include <time.h>

// 宏定义
#define WIDTH 10
#define HEIGHT 10

//变量声明部分
typedef struct
{
	int x;      //玩家x坐标
	int y;      //玩家y坐标
	int boxs;   //剩余箱子数量
	int lives;  //玩家生命值
}Gamer; //玩家状态结构体

typedef struct 
{
    int cnt;          //排名
    char name[10];    //玩家名字
    float time;       //游戏时间
} Data;

Gamer gamer;
Data data[100];

int paused = 0; // 游戏是否暂停的标志
char user[10];  //玩家名字
int map[HEIGHT][WIDTH];  //地图数组
int level = 1; //默认关卡为1 

// 文件名
const char *FILE_LOG[] = {"log1.txt", "log2.txt", "log3.txt"};
const char *FILE_NUM[] = {"num1.txt", "num2.txt", "num3.txt"};
const char *FILE_RANK[] ={"rank1.txt", "rank2.txt", "rank3.txt"};

// 定义地图数组,二维数组有两个维度,而地图也是二维的矩形
int Map3[3][HEIGHT][WIDTH] =
{
	{ //第一关
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    	0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
    	0, 0, 0, 1, 4, 0, 1, 0, 0, 0,
    	0, 0, 0, 1, 0, 0, 0, 1, 1, 1,
    	0, 1, 1, 1, 3, 0, 0, 3, 4, 1,
    	0, 1, 4, 0, 3, 2, 0, 1, 1, 1,
    	0, 1, 1, 1, 1, 3, 0, 1, 0, 0,
    	0, 0, 0, 0, 1, 4, 0, 1, 0, 0,
    	0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
    	0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	},
	{ //第二关
		0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
		0, 1, 4, 0, 0, 0, 4, 1, 1, 0,
		0, 1, 4, 0, 0, 0, 3, 4, 1, 0,
		1, 1, 1, 0, 0, 0, 0, 3, 1, 1,
		1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
		1, 0, 3, 0, 0, 0, 0, 0, 0, 1,
		1, 0, 1, 1, 3, 1, 1, 1, 0, 1,
		1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
		1, 0, 0, 0, 0, 2, 0, 0, 0, 1,
		1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	},
	{ //第三关
		1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
		1, 0, 4, 0, 1, 0, 0, 0, 1, 1,
		1, 0, 0, 0, 1, 0, 0, 0, 0, 1,
		1, 0, 0, 0, 0, 0, 3, 0, 0, 1,
		1, 0, 0, 0, 1, 0, 0, 0, 0, 1,
		1, 1, 3, 0, 1, 0, 0, 0, 0, 1,
		1, 1, 4, 0, 4, 0, 1, 4, 0, 1,
		1, 1, 1, 1, 3, 3, 1, 1, 1, 1,
		0, 0, 0, 1, 0, 2, 1, 0, 0, 0,
		0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
	}
};

//函数声明部分 
void initData();	//初始化地图数据 
void drawMap();		//绘制地图
void move(char direction); //移动函数
void togglePause(); //暂停游戏
void ShowStart();   //显示开始界面
void showHelp();    //显示帮助
void Console();     //调整控制台格式
void log(const char *filename, int cnt, float time, char *name); // 游戏用时记录,写入文件
int num(const char *filename);      //统计游戏执行次数
void selectLevel();     //选择关卡
void rank(const char *input, const char *output);   //对log内记录的数据进行排序
int cmp(const void *a, const void *b);  // 排序,用于qsort函数
void ShowRank(const char *filename);    //显示排行榜

int main() 
{
    char direction; //移动方向
	Console();
    selectLevel();
    int cnt = num(FILE_NUM[level - 1]);   //读取游戏执行次数

    clock_t start, end;
    double elapsed_time;
    start = clock();//计时起点	

    while(1)
    {
    	// 游戏暂停
        if (paused) 
        {
            printf("按任意键继续...");
            getch();
            paused = 0;
        }
        
        system("cls"); // 清除屏幕
        printf("第%d关\n人物血量: %d\n", level, gamer.lives);
        drawMap(); // 绘制地图
        
        direction = getch(); // 获取玩家输入的方向
		
        //控制玩家移动
        move(direction);
        
        if(!gamer.boxs)
        {
            end = clock();//计时终点 
        	elapsed_time = ((double)end - start) / CLOCKS_PER_SEC;//计算通关时长
            
        	system("color 0A"); //更换控制台颜色为绿色

            printf("大侠请留名:\n");
            scanf("%s", user);

            //记录和排序
            log(FILE_LOG[level - 1], cnt, elapsed_time, user);
            rank(FILE_LOG[level - 1], FILE_RANK[level - 1]);

            printf("恭喜%s完成游戏!用时%.2f s, 超越了99.99%%的人\n", user, (double)clock() / CLOCKS_PER_SEC);

            break;
        }

        //显示菜单,暂停,显示排行榜功能
        switch (direction)
        {
            case 'p':
                togglePause();
                break;
            case 't':
                showHelp();
                printf("\n请按任意键继续\n");
                getch();
                break;
            case 'r':
                ShowRank(FILE_RANK[level - 1]);
                printf("\n请按任意键继续\n");
                getch();
                break;
            case 27:
                return 0;
        }
        }  
    return 0;
}

/*
*函数名称:initData
*函数功能:根据选择的关卡载入对应的地图,并且记录人物的位置 
*函数参数:无 
*返回值:无 
*/
void initData()
{
	gamer.lives = 3;
    int i, j;
    for(int i = 0; i < HEIGHT; i++)
    	for(int j = 0; j < WIDTH; j++)
    		map[i][j] = Map3[level-1][i][j];
    for(i = 0; i < HEIGHT; i++)
    {
        for(j = 0; j < WIDTH; j++)
        {
            if(map[i][j] == 2)
            {
                gamer.x = j;
                gamer.y = i;
            } 
            if(map[i][j] == 3)
            {
                gamer.boxs++;
            }
        }
    }
}


/*
*函数名称:drawMap
*函数功能:绘制地图函数,根据地图数组显示地图内容
*函数参数:无 
*返回值:无 
*/
void drawMap()
{
    int i, j;
    for(i = 0; i < WIDTH; i++)
	{
        for(j = 0; j < HEIGHT; j++)
		{
            switch(map[i][j])
			{
                case 0: 
					printf(" "); 
					break;
                case 1: 
					printf("■"); //标识墙 
					break; 
                case 2: 
					printf("♀"); //标识玩家位置,♀的ASCII码是9792 
					break; 
                case 3: 
					printf("◆"); //标识箱子的位置 
					break; 
                case 4: 
					printf("●"); //标识需要将箱子推入的位置 
					break; 
                case 5: 
					printf("★"); //标识已经完成的箱子 
					break; 
            }
        }
        printf("\n");
    }
}
/*
*函数名称:move
*函数功能:根据输入的方向移动人物,执行移动逻辑
*函数参数:direction 移动方向 
*返回值:无
*/
void move(char direction)
{
    /*
    *变量含义:new_x、new_y表示人物的新位置
              ub_x、ub_y表示箱子的新位置
    */
    int new_x, new_y, ub_x, ub_y;

    //根据输入的方向确定新位置
    switch(direction)
    {
        case 'w':
            new_x = gamer.x;
            new_y = gamer.y - 1;
            ub_x = new_x;
            ub_y = new_y - 1;
            break;
        case 'a':
            new_x = gamer.x - 1;
            new_y = gamer.y;
            ub_x = new_x - 1;
            ub_y = new_y;
            break;
        case 's':
            new_x = gamer.x;
            new_y = gamer.y + 1;
            ub_x = new_x;
            ub_y = new_y + 1;
            break;
        case 'd':
            new_x = gamer.x + 1;
            new_y = gamer.y;
            ub_x = new_x + 1;
            ub_y = new_y;
            break;
    }

    // 检查新位置是否越界
    if (new_x < 0 || new_x >= WIDTH || new_y < 0 || new_y >= HEIGHT) {
        return;
    }

    // 检查新位置是否为墙、终点、已完成的箱子
    if(map[new_y][new_x] == 5 || map[new_y][new_x] == 1 || map[new_y][new_x] == 4)
	{
        return;
    }

    //如果新位置为箱子,再进行下一步判断
    if(map[new_y][new_x] == 3)
	{
        // 判断箱子前方是否为墙
        if(map[ub_y][ub_x] == 1)
		{
            return;
        }
        
        // 判断箱子前方是否为终点
        if(map[ub_y][ub_x] == 4)
		{
            // 将箱子前方内容赋值为已完成的箱子
            map[ub_y][ub_x] = 5;
            map[new_y][new_x] = 0;
            
            // 箱子的数目减1 
            gamer.boxs--; 
        }
		else
		{
            // 移动箱子
            map[ub_y][ub_x] = 3; 
        }
    }
    // 移动人
    map[gamer.y][gamer.x] = 0;
    map[new_y][new_x] = 2;
    // 更新人的坐标
    gamer.y = new_y; 
    gamer.x = new_x;
}


/*
*函数名称: togglePause
*函数功能:切换游戏暂停状态的函数
*函数参数:无 
*返回值:无 
*/
void togglePause()
{
	/*
	如果果 paused 当前为 true,取反后它将变成 false,表示游戏从暂停状态变为未暂停;
	反之,如果 paused 当前为 false,取反后它将变成 true,表示游戏从未暂停状态变为暂停。
	这样,只需一行代码就可以实现状态的切换,
	*/
    paused = !paused;
    if (paused) 
	{
        printf("游戏已暂停\n");
    } 
	else 
	{
        printf("游戏已恢复\n");
    }
}

/*
*函数名称: ShowStart
*函数功能:显示游戏开始界面的函数
*函数参数:无 
*返回值:无
*/
void ShowStart()
{
    printf("游戏加载中,请稍后........."); 
	printf("\n\n请选择关卡:\n");
	printf("|      第一关       |\n"); 
	printf("|      第二关       |\n"); 
	printf("|      第三关       |\n"); 
}
/*
*函数名称: showHelp
*函数功能:显示游戏帮助信息的函数
*函数参数:无 
*返回值:无 
*/
void showHelp()
{
    printf("\n游戏帮助:\n");
    printf("- w/a/s/d:上下左右移动\n");
    printf("- p:暂停/恢复游戏\n");
    printf("- t:显示帮助\n");
    printf("- r:查看排行榜\n");
    printf("- ESC:退出游戏\n");
}

/*
*函数名称: selectLevel
*函数功能:新增的关卡选择函数
*函数参数:无 
*返回值:无 
*/
void selectLevel()
{
    int input;
    ShowStart();
    showHelp();
    do 
    {
    	printf("\n请输入1-3选择对应关卡\n");
        input = getch() - '0'; // 将输入的字符转换为整数
    } while (input < 1 || input > 3);

    level = input;
    initData();
}


/*
*函数名称: Console
*函数功能:调整控制台格式 
*函数参数:无 
*返回值:无 
*/
void Console()
{
	//清除光标闪烁 
	CONSOLE_CURSOR_INFO cursor;
	cursor.bVisible = FALSE;
	cursor.dwSize = 1;
	HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorInfo(output, &cursor);
	
	SetConsoleTitle("推箱子小游戏"); //更换控制台标题
}

/*
*函数名称: log
*函数功能:记录完成游戏用时,输入到指定文件中 
*函数参数:const char* filename-写入文件名,int cnt-程序运行次数 , float time-完成游戏用时, char *name 玩家名字 
*返回值:无 
*/
void log(const char* filename, int cnt, float time, char *name)
{
	FILE *fp = fopen(filename,"a");
	if(fp == NULL)
		return;
	else
	{
		fprintf(fp, "%d  %s %.2f\n", cnt, name, time);
	}
	fclose(fp);
} 


/*
*函数名称: num
*函数功能:统计程序执行次数,并且更新到文件num.txt中
*函数参数:const char* filename-写入文件名
*返回值:返回更新前的计数值 
*/
int num(const char *filename)
{
    FILE *fp = fopen(filename, "r+");
    int cnt = 0;

    // 检查文件是否能打开
    if (fp == NULL)
    {
        printf("无法打开文件'%s': %s\n", filename,strerror(errno));
        exit(EXIT_FAILURE); // 如果无法打开,退出程序
    }

    fscanf(fp, "%d", &cnt); 
    cnt++;
    fseek(fp, 0, SEEK_SET);
    fprintf(fp, "%d", cnt);
    fflush(fp);
    
    fclose(fp);
    return cnt - 1; // 返回更新前的计数值
}

/*
*函数名称:cmp
*函数功能:比较函数,用于qsort函数
*函数参数:const void *a, const void *b-比较的两个数据
*返回值:int-比较结果,负值表示a在b之前,正值表示a在b之后,0表示a和b相等
*/
int cmp(const void *a, const void *b) 
{
    const Data *da = (const Data *)a;
    const Data *db = (const Data *)b;
    return da->time - db->time; // 升序排序,负值表示a在b之前
}

/*
*函数名称:rank
*函数功能:对文件进行排序,将用时从低到高排序
*函数参数:const char* input-读取文件名,const char *output 写入文件名 
*返回值:无
*/
void rank(const char *input, const char *output)
{

    FILE *fp = fopen(input, "r+");
    if (fp == NULL)
    {
        printf("无法打开文件'%s': %s\n", input, strerror(errno));
        exit(EXIT_FAILURE); // 如果无法打开,退出程序
    }

    int i = 0;
    while (!feof(fp)) 
    {
        fscanf(fp, "%d %s %f\n", &(data+i)->cnt, &(data+i)->name, &(data+i)->time);
        i++;
    }
    
    fclose(fp);

    // 对数据进行排序
    int dataSize = i;
    qsort(data, dataSize, sizeof(data[0]), cmp);

    // 将排序后的内容写入新文件
    FILE *outFp = fopen(output, "w");
    if (outFp == NULL)
    {
        printf("无法打开文件'%s'进行写入: %s\n", output, strerror(errno));
        exit(EXIT_FAILURE);
    }
    for (int j = 0; j < dataSize; j++)
    {
        fprintf(outFp, "%d %s %.2f\n", data[j].cnt, data[j].name, data[j].time);
    }
    fclose(outFp);
}


/*
*函数名称:ShowRank
*函数功能:显示排行榜
*函数参数:const char *filename - p排行榜记录文件
*返回值:无
*/
void ShowRank(const char *filename)
{
   FILE *fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("无法打开文件'%s': %s\n", filename, strerror(errno));
        exit(EXIT_FAILURE); // 如果无法打开,退出程序
    }

    int dataSize;
    fseek(fp, 0, SEEK_END);
    long fileSize = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    // 计算数据条数
    dataSize = fileSize / sizeof(Data);
    if (dataSize > 100)
    {
        printf("文件中数据过多,只显示前%d条\n", 100);
        dataSize = 100;
    }

    printf("\n排行榜:\n");
    for (int i = 0; i < dataSize && fscanf(fp, "%d %s %f\n", &(data + i)->cnt, &(data + i)->name, &(data + i)->time) == 3; i++)
    {
        printf("%d %s %.2f\n", i+1, data[i].name, data[i].time);
    }
    fclose(fp);
}

使用说明

需要自己创建项目然后创建正确路径的所有文件才能正常运行,推箱子的逻辑有待优化,例如目前人不能到达中箱子目标纵终点等。再比如日志功能如果某次运行中途退出也会记录运行次数,以及排行榜功能可能个别数据无法正确排序。仅仅借此来锻炼C语言语法还是不错的。


本程序编写过程有参考:
C语言实现推箱子游戏——ZackSock
C语言小游戏快速入门–推箱子——菜鸟凌凌漆

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
推箱子是一款经典的小游戏,也是Java大作业中常见的题目之一。这款游戏的目标是将所有的箱子都推到指定位置,通过合理的操作来完成任务。 推箱子游戏通常由地图、角色、箱子和目标点等元素组成。在游戏开始时,地图上会散落着一些箱子、墙壁和空地。角色则需要通过移动来将箱子推到指定位置,完成所有任务。 在Java中实现推箱子游戏,可以使用图形界面库如Swing或JavaFX来构建游戏界面。首先,我们需要设计一个游戏地图,可以使用二维数组来代表地图,其中不同的数字代表不同的元素,如0代表空地,1代表墙壁,2代表箱子,3代表目标点等。 然后,需要实现角色的移动功能。可以通过监听键盘事件来控制角色的上下左右移动,并判断移动的合法性,例如判断是否会撞到墙壁或箱子等。 箱子的推动需要考虑多种情况,如判断箱子是否能被推动、箱子的前方是否有空地或目标点等。在推动箱子后,需要判断箱子是否被推到了目标点上,以便判断游戏是否胜利。 最后,可以添加一些游戏辅助功能,如计时器、重玩功能、关卡选择等,提升游戏的可玩性和趣味性。 总之,通过使用Java语言和合适的图形界面库,我们可以实现一个简单的推箱子小游戏。学生可以在实现游戏的基础功能后,进一步优化设计、增加关卡、美化界面等,展示自己的创意和编程能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值