目录
第一次发布文章,写的可能有点多,想直接看码的可以跳到代码整合目录,程序中也有注释,也可以移步用C语言实现推箱子小游戏基础程序plus_Cheat_陈十一的博客-CSDN博客,全新推出的优化过的plus代码
设计一个简单的推箱子游戏
该游戏是通过控制人的走向来移动箱子,箱子只能向前推,不能向后拉,且一次只能推动一个箱子。
功能设计要求:
- 人机操控平台:启动程序后,系统提供给用户一个操作界面,以便用户有效操作游戏。
- 创建并绘制地图:推箱子游戏需要创建不同的地图以增加游戏的趣味性。
- 选择地图:系统应提供两个及以上不同难度的地图以供用户选择。
- 移动操作:本游戏主要通过人或人和箱子的移动来进行的。系统接收用户输入一个字符(按键)来控制人的走向,并且可以在允许的情况下推动箱子。
- 移动步数和得分:移动步数是统计从开始游戏到游戏结束(通关)所走的总步数,在游戏过程中这是实时变化的。得分是统计每将一个箱子移动到目的地所获得的分数,只有当把所有箱子移动到指定目标位置后游戏结束(通关)。
- 游戏操作说明:系统给用户提供地图元素组成、操作规则等信息
代码实现
基础代码要求
基本数据定义
代码实现主要使用graphics.h和conio.h库实现人机交互等。
用死循环对键盘进行轮询检测键盘输入。
代码初期参考第9章 推箱子 - CodeBus,初学graphics.h。
下面介绍本程序中运用到的graphics.h中的一些函数。#include <stdio.h>
#include <stdio.h> #include <graphics.h> #include <stdlib.h> #include <conio.h>
#define Box_Size 60//每个格子所占像素大小 #define Box_Num 9//格子数 //记录玩家位置 struct Player { int x; int y; }; Player player;
enum Element{empty,wall,role,target,box,achieve,data_bg,role_target}; //empty=0表示空地,wall=1表示墙,role=2玩家位置,target=3目标位置,box=4箱子位置,achieeve=5成功位置(箱子位置与目标位置重合时),data_bg=6数据背景,role_target=7当role与target重合时 int origin_windows[Round_Num][Box_Num][Box_Num+1] = { { {1,1,1,1,1,1,1,1,1,6}, {1,1,0,0,0,1,1,1,1,6}, {1,1,0,0,0,1,0,0,1,6}, {1,1,1,0,0,0,0,3,1,6}, {1,1,1,0,1,1,1,3,1,6}, {1,0,4,0,1,1,1,3,1,6}, {1,0,4,4,1,1,1,1,1,6}, {1,2,0,0,1,1,1,1,1,6}, {1,1,1,1,1,1,1,1,1,6}, }, };
enum Element方便书写已经后续查验代码时使用
//第一关卡地图 int First_Round[Box_Num][Box_Num+1]; /* * targetNum目标数目 * achievementNum成功数目 * steps记录步数 * Mode 切换模式,用于重玩 */ int targetNum = 0, achievementNum = 0, steps, Mode = 0;
主要函数
int main() { Windows_Init(); while (1) { if (Mode == 0) Windows_Show(); else { Windows_Init(); Mode = 0; } Flash_Frame(); }; return 0; }
先确定主要实现流程:窗口初始化->通过First_Round数组元素进行游戏场景展示->轮询键盘->改变First_Round数组元素->重新进行场景展示->...->游戏结束
则定义函数
void Windows_Init(void);//窗口及数据初始化
void Windows_Show(void);//展示游戏窗口
void Flash_Frame(void);//轮询键盘
Windows_Init函数
void Windows_Init(void)
{
initgraph(Box_Num * Box_Size+80, Box_Num * Box_Size);
setbkcolor(RGB(255, 255, 255));
BeginBatchDraw();
int i, j;
targetNum = 0;
//遍历第一关卡
for (i = 0; i < Box_Num; i++)
{
for (j = 0; j < Box_Num; j++)
{
First_Round[i][j] = origin_windows[0][i][j];//将所有关卡中的某一关赋给本轮游戏的关卡数组
if (origin_windows[0][i][j] == role)
{
player.x = i;
player.y = j;
}
else if (origin_windows[0][i][j] == target)
targetNum++;
}
}
achievementNum = 0;
steps = 0;
}
initgraph(w,h):设置一个w宽h高的绘图窗口
setbkcolor():设置绘图窗口背景色
BeginBatchDraw():用于开始批量绘图。执行后,任何绘图操作都将暂时不输出到绘图窗口上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出。
Windows_Show函数
void Windows_Show()
{
int i, j;
//cleardevice() :清空屏幕,之后会界面内容全部清空,显示为默认背景颜色。
cleardevice();
//遍历二维关卡数据
for (i = 0; i < Box_Num; i++)
{
for (j = 0; j < Box_Num; j++)
{
if (First_Round[i][j] == empty||First_Round[i][j]==data_bg)//空格为白色
{
//setfillcolor():设置填充绘制图形式所用的颜色。
setfillcolor(RGB(255, 255, 255));
setlinecolor(RGB(255, 255, 255));
fillrectangle(j * Box_Size, i * Box_Size, (j + 1) * Box_Size, (i + 1) * Box_Size);
}
else if (First_Round[i][j] == wall)//墙的样式
{
setfillcolor(RGB(0x93, 0x84, 0x5c));
setlinecolor(RGB(255, 255, 255));
fillrectangle(j * Box_Size, i * Box_Size, (j + 1) * Box_Size, (i + 1) * Box_Size);
}
else if (First_Round[i][j] == role || First_Round[i][j] == role_target)
{
setfillcolor(RGB(0xf5, 0xc3, 0x42));
setlinecolor(RGB(255, 255, 255));
fillcircle((j + 0.5) * Box_Size, (i + 0.5) * Box_Size, 0.5 * Box_Size);
}
else if (First_Round[i][j] == box)//盒子的样式
{
setfillcolor(RGB(0, 0, 255));
setlinecolor(RGB(255, 255, 255));
fillrectangle(j * Box_Size, i * Box_Size, (j + 1) * Box_Size, (i + 1) * Box_Size);
}
else if (First_Round[i][j] == target)//目标点的样式
{
setfillcolor(RGB(0, 255, 150));
setlinecolor(RGB(255, 255, 255));
fillrectangle((j + 0.3) * Box_Size, (i + 0.3) * Box_Size, (j + 0.7) * Box_Size, (i + 0.7) * Box_Size);
}
else if (First_Round[i][j] == achieve)成功的样式
{
setfillcolor(RGB(15, 99, 60));
setlinecolor(RGB(255, 255, 255));
fillcircle((j + 0.5) * Box_Size, (i + 0.5) * Box_Size, 0.5 * Box_Size);
}
}
}
//OPAQUE不透明
//SetBkMode函数来设置DrawText函数的输出方式
wchar_t step_str[10];
setbkmode(OPAQUE);
settextcolor(RGB(0, 0, 0));
settextstyle(30, 0, _T("宋体"));
outtextxy(Box_Size * Box_Num + 5, 60, _T("steps"));
_stprintf_s(step_str, _T("%d"), steps);
outtextxy(Box_Size * Box_Num + 5, 90, step_str);//记步
wchar_t score_str[10];
setbkmode(OPAQUE);
settextcolor(RGB(0, 0, 0));
settextstyle(30, 0, _T("宋体"));
outtextxy(Box_Size*Box_Num+5, 120, _T("score"));
_stprintf_s(score_str, _T("%d"), achievementNum);//计分
outtextxy(Box_Size * Box_Num + 5, 150, score_str);
if (check_lose())
{
//TRANSPARENT透明
setbkmode(TRANSPARENT);
settextcolor(RGB(255, 255, 0));
settextstyle(80, 0, _T("宋体"));
outtextxy(80, 200, _T("lose"));
}
else if (achievementNum == targetNum)
{
setbkmode(TRANSPARENT);
settextcolor(RGB(255, 255, 0));
settextstyle(80, 0, _T("宋体"));
outtextxy(80, 200, _T("win"));
}
FlushBatchDraw();
}
cleardevice() :清空屏幕,之后会界面内容全部清空,显示为默认背景颜色。(一般用在更改背景色后)
setfillcolor(RGB( , , )):设置填充绘制图形式所用的颜色。
setlinecolorRGB()):设置线的填充样式
fillrectangle(x1,y1,x2,y2):长方形,左上顶点(x1,y1),右下顶点(x2,y2)
setbkmode():设置text的输出属性,OPAQUE不透明,TRANSPARENT透明
settextcolor(RGB()):设置text颜色
#其中RGB()也可为0xff000000,分组形式为0x|ff|000000,ff为透明度,000000为RGB
settextstyle(cHeight,cWidth,ctype);cHeight指定高度,cWidth字符的平均宽度(0为自适应),ctype字体名称
outtextxy(x,y,s),在起始位置为(x,y)处输出s
#参考EasyX 文档 - outtextxy,s的具体字符类型可以在程序实现过程中尝试
#例如当我们为outtextxy传入char s[10]的参数时,会报
无法将参数 1 从“char [10]”转换为“wchar_t *const ”
的错误,此时将char s更改为wchar_t s;
#使用_stprintf_s(s, _T("%d"), Num);来将数字转为wchar_t类型的字符串。
FlushBatchDraw():执行未完成的绘制任务
随后根据First_Round中不同的元素属性绘制不同的图案
void Flash_Frame()函数
在此函数中进行键盘轮询以及对关卡数组进行相应的改变
使用conio.h库与键盘进行交互
先写一个上移操作
char input; int role_x,role_y;//当收到键盘操作时,人物即将移动到的点位 //_kbhit检查当前是否有键盘输入,若有则返回一个非0值,否则返回0 if (_kbhit() && (achievementNum < targetNum)) { //getch从键盘读取单个字符。 但它不使用任何缓冲区,因此输入的字符会立即返回,无需等待回车键。 input = _getch(); steps++; switch (input) { case('w')://上移 role_x=player.x-1; if (First_Round[role_x][player.y] == empty) { if (First_Round[player.x][player.y] == role)//如果此处是role First_Round[player.x][player.y] = empty; else//如果此处是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role; player.x = role_x; } else if (First_Round[role_x][player.y] == target) { if (First_Round[player.x][player.y] == role)//如果此处是role First_Round[player.x][player.y] = empty; else//如果此处是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role_target; player.x = role_x; } else if ((First_Round[role_x][player.y] == box|| First_Round[role_x][player.y] == achieve) && (First_Round[role_x - 1][player.y] == empty|| First_Round[role_x - 1][player.y] == target))//判断有wall { if (First_Round[role_x][player.y] == box) { if (First_Round[player.x][player.y] == role)//如果此处是role First_Round[player.x][player.y] = empty; else//如果此处是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role; player.x = role_x; if (First_Round[role_x - 1][player.y] == target)//如果box和target重叠,合为achieve { First_Round[role_x - 1][player.y] = achieve; achievementNum++; } else First_Round[role_x - 1][player.y] = box; } else if(First_Round[role_x][player.y] == achieve) { achievementNum--; if (First_Round[player.x][player.y] == role)//此位置是玩家位置 First_Round[player.x][player.y] = empty;//此位置变为空 else //此位置是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role_target; //将box从target中推走,则此位置变成role_target player.x = role_x; if (First_Round[role_x - 1][player.y] == target)//如果box和target重叠,合为achieve { First_Round[role_x - 1][player.y] = achieve; achievementNum++; } else First_Round[role_x - 1][player.y] = box; } } break;
此函数没有新内容,看码就行
并且类推写出左右下移,放入switch中
case('r'): Mode = 1; break;
r用于重玩游戏
代码整合
#include <stdio.h> #include <graphics.h> #include <stdlib.h> #include <time.h> #include <conio.h> #define Box_Size 60 #define Box_Num 9 #define Round_Num 3 //记录玩家位置 struct Player { int x; int y; }; Player player,playernext; enum Element{empty,wall,role,target,box,achieve,data_bg,role_target}; //1表示墙,0表示空地,2表示玩家位置,3目标位置,4箱子位置 int origin_windows[Round_Num][Box_Num][Box_Num+1] = { { {1,1,1,1,1,1,1,1,1,6}, {1,1,0,0,0,1,1,1,1,6}, {1,1,0,0,0,1,0,0,1,6}, {1,1,1,0,0,0,0,3,1,6}, {1,1,1,0,1,1,1,3,1,6}, {1,0,4,0,1,1,1,3,1,6}, {1,0,4,4,1,1,1,1,1,6}, {1,2,0,0,1,1,1,1,1,6}, {1,1,1,1,1,1,1,1,1,6}, }, }; int last_step[Box_Num][Box_Num+1],First_Round[Box_Num][Box_Num+1]; int targetNum = 0, achievementNum = 0, steps, Mode = 0; int check_lose() { int i, j; for (i = 1; i < Box_Num - 1; i++) { for (j = 1; j < Box_Num - 1; j++) { if (First_Round[i][j] == box && (First_Round[i + 1][j] == wall && First_Round[i][j + 1] == wall || First_Round[i - 1][j] == wall && First_Round[i][j - 1] == wall)) return 1; } } return 0; } void Windows_Init(void) { initgraph(Box_Num * Box_Size+80, Box_Num * Box_Size); setbkcolor(RGB(255, 255, 255)); BeginBatchDraw(); int i, j; targetNum = 0; //遍历第一关卡 for (i = 0; i < Box_Num; i++) { for (j = 0; j < Box_Num; j++) { First_Round[i][j] = origin_windows[0][i][j];//将所有关卡中的某一关赋给本轮游戏的关卡数组 if (origin_windows[0][i][j] == role) { player.x = i; player.y = j; } else if (origin_windows[0][i][j] == target) targetNum++; } } achievementNum = 0; steps = 0; } void Windows_Show() { int i, j; //cleardevice() :清空屏幕,之后会界面内容全部清空,显示为默认背景颜色。 cleardevice(); //遍历二维关卡数据 for (i = 0; i < Box_Num; i++) { for (j = 0; j < Box_Num; j++) { if (First_Round[i][j] == empty||First_Round[i][j]==data_bg) { //setfillcolor():设置填充绘制图形式所用的颜色。 setfillcolor(RGB(255, 255, 255)); setlinecolor(RGB(255, 255, 255)); fillrectangle(j * Box_Size, i * Box_Size, (j + 1) * Box_Size, (i + 1) * Box_Size); } else if (First_Round[i][j] == wall) { setfillcolor(RGB(0x93, 0x84, 0x5c)); setlinecolor(RGB(255, 255, 255)); fillrectangle(j * Box_Size, i * Box_Size, (j + 1) * Box_Size, (i + 1) * Box_Size); } else if (First_Round[i][j] == role || First_Round[i][j] == role_target) { setfillcolor(RGB(0xf5, 0xc3, 0x42)); setlinecolor(RGB(255, 255, 255)); fillcircle((j + 0.5) * Box_Size, (i + 0.5) * Box_Size, 0.5 * Box_Size); } else if (First_Round[i][j] == box) { setfillcolor(RGB(0, 0, 255)); setlinecolor(RGB(255, 255, 255)); fillrectangle(j * Box_Size, i * Box_Size, (j + 1) * Box_Size, (i + 1) * Box_Size); } else if (First_Round[i][j] == target) { setfillcolor(RGB(0, 255, 150)); setlinecolor(RGB(255, 255, 255)); fillrectangle((j + 0.3) * Box_Size, (i + 0.3) * Box_Size, (j + 0.7) * Box_Size, (i + 0.7) * Box_Size); } else if (First_Round[i][j] == achieve) { setfillcolor(RGB(15, 99, 60)); setlinecolor(RGB(255, 255, 255)); fillcircle((j + 0.5) * Box_Size, (i + 0.5) * Box_Size, 0.5 * Box_Size); } } } //OPAQUE不透明 //SetBkMode函数来设置DrawText函数的输出方式 wchar_t step_str[10]; setbkmode(OPAQUE); settextcolor(RGB(0, 0, 0)); settextstyle(30, 0, _T("宋体")); outtextxy(Box_Size * Box_Num + 5, 60, _T("steps")); _stprintf_s(step_str, _T("%d"), steps); outtextxy(Box_Size * Box_Num + 5, 90, step_str); wchar_t score_str[10]; setbkmode(OPAQUE); settextcolor(RGB(0, 0, 0)); settextstyle(30, 0, _T("宋体")); outtextxy(Box_Size*Box_Num+5, 120, _T("score")); _stprintf_s(score_str, _T("%d"), achievementNum); outtextxy(Box_Size * Box_Num + 5, 150, score_str); if (check_lose()) { //TRANSPARENT透明 setbkmode(TRANSPARENT); settextcolor(RGB(255, 255, 0)); settextstyle(80, 0, _T("宋体")); outtextxy(80, 200, _T("lose")); } else if (achievementNum == targetNum) { setbkmode(TRANSPARENT); settextcolor(RGB(255, 255, 0)); settextstyle(80, 0, _T("宋体")); outtextxy(80, 200, _T("win")); } FlushBatchDraw(); } void Flash_Frame() { char input; int role_x,role_y;//当收到键盘操作时,人物即将移动到的点位 //_kbhit检查当前是否有键盘输入,若有则返回一个非0值,否则返回0 if (_kbhit() && (achievementNum < targetNum)) { //getch从键盘读取单个字符。 但它不使用任何缓冲区,因此输入的字符会立即返回,无需等待回车键。 input = _getch(); steps++; switch (input) { case('w')://上移 role_x=player.x-1; if (First_Round[role_x][player.y] == empty) { if (First_Round[player.x][player.y] == role)//如果此处是role First_Round[player.x][player.y] = empty; else//如果此处是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role; player.x = role_x; } else if (First_Round[role_x][player.y] == target) { if (First_Round[player.x][player.y] == role)//如果此处是role First_Round[player.x][player.y] = empty; else//如果此处是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role_target; player.x = role_x; } else if ((First_Round[role_x][player.y] == box|| First_Round[role_x][player.y] == achieve) && (First_Round[role_x - 1][player.y] == empty|| First_Round[role_x - 1][player.y] == target))//判断有wall { if (First_Round[role_x][player.y] == box) { if (First_Round[player.x][player.y] == role)//如果此处是role First_Round[player.x][player.y] = empty; else//如果此处是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role; player.x = role_x; if (First_Round[role_x - 1][player.y] == target)//如果box和target重叠,合为achieve { First_Round[role_x - 1][player.y] = achieve; achievementNum++; } else First_Round[role_x - 1][player.y] = box; } else if(First_Round[role_x][player.y] == achieve) { achievementNum--; if (First_Round[player.x][player.y] == role)//此位置是玩家位置 First_Round[player.x][player.y] = empty;//此位置变为空 else //此位置是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role_target; //将box从target中推走,则此位置变成role_target player.x = role_x; if (First_Round[role_x - 1][player.y] == target)//如果box和target重叠,合为achieve { First_Round[role_x - 1][player.y] = achieve; achievementNum++; } else First_Round[role_x - 1][player.y] = box; } } break; case('s')://下移 role_x = player.x + 1; if (First_Round[role_x][player.y] == empty) { if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role; player.x = role_x; } else if (First_Round[role_x][player.y] == target) { if (First_Round[player.x][player.y] == role)//如果此处是role First_Round[player.x][player.y] = empty; else//如果此处是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role_target; player.x = role_x; } else if ((First_Round[role_x][player.y] == box || First_Round[role_x][player.y] == achieve) && (First_Round[role_x + 1][player.y] == empty || First_Round[role_x + 1][player.y] == target)) { if (First_Round[role_x][player.y] == box) { if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[role_x][player.y]=role; player.x = role_x; if (First_Round[role_x + 1][player.y] == target)//如果box和target重叠,合为achieve { First_Round[role_x + 1][player.y] = achieve; achievementNum++; } else First_Round[role_x + 1][player.y] = box; } else if (First_Round[role_x][player.y] == achieve) { achievementNum--; if (First_Round[player.x][player.y] == role)//此位置是玩家位置 First_Round[player.x][player.y] = empty;//此位置变为空 else //此位置是role_target First_Round[player.x][player.y] = target; First_Round[role_x][player.y] = role_target; //将box从target中推走,则此位置变成role_target player.x = role_x; if (First_Round[role_x + 1][player.y] == target)//如果box和target重叠,合为achieve { First_Round[role_x + 1][player.y] = achieve; achievementNum++; } else First_Round[role_x + 1][player.y] = box; } } break; case('a')://左移 role_y = player.y - 1; if (First_Round[player.x][role_y] == empty) { if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[player.x][role_y] = role; player.y = role_y; } else if (First_Round[player.x][role_y] == target) { if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[player.x][role_y] = role_target; player.y = role_y; } else if ((First_Round[player.x][role_y] == box || First_Round[player.x][role_y] == achieve)&& (First_Round[player.x][role_y-1] == empty || First_Round[player.x][role_y - 1] == target)) { if (First_Round[player.x][role_y] == box) { if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[player.x][role_y] = role; player.y = role_y; } else if (First_Round[player.x][role_y] == achieve) { achievementNum--; if (First_Round[player.x][player.y] == role)//此位置是玩家位置 First_Round[player.x][player.y] = empty;//此位置变为空 else if (First_Round[player.x][player.y] == role_target) First_Round[player.x][player.y] = target; First_Round[player.x][role_y] = role_target; } if (First_Round[player.x][role_y - 1] == target)//如果box和target重叠,合为achieve { First_Round[player.x][role_y - 1] = achieve; achievementNum++; } else First_Round[player.x][role_y - 1] = box; } break; case('d')://右移 role_y = player.y + 1; if (First_Round[player.x][role_y] == empty) { if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[player.x][role_y] = role; player.y = role_y; } else if (First_Round[player.x][role_y] == target) { if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[player.x][role_y] = role_target; player.y = role_y; } else if ((First_Round[player.x][role_y] == box || First_Round[player.x][role_y] == achieve)&& (First_Round[player.x][role_y + 1] == empty || First_Round[player.x][role_y + 1] == target)) { if (First_Round[player.x][role_y] == box) { if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[player.x][role_y] = role; player.y = role_y; } else { achievementNum--; if (First_Round[player.x][player.y] == role) First_Round[player.x][player.y] = empty; else First_Round[player.x][player.y] = target; First_Round[player.x][role_y] = role_target; player.y = role_y; } if (First_Round[player.x][role_y + 1] == target)//如果box和target重叠,合为achieve { First_Round[player.x][role_y + 1] = achieve; achievementNum++; } else First_Round[player.x][role_y + 1] = box; } break; case('r'): Mode = 1; break; default: break; } } } int main() { Windows_Init(); while (1) { if (Mode == 0) Windows_Show(); else { Windows_Init(); Mode = 0; } Flash_Frame(); }; return 0; }
代码整合
成果展示
代码优化方向
此程序代码过于冗长,并且进行了许多重复操作,并且功能不完善,注意到steps不是有效步数,achievementNum和score是等价的。
那么可以:模块化程序+定义重复操作的函数
可以利用转置矩阵+上移操作来替换switch中的大量代码,例如左移操作便是将原矩阵顺时针旋转90后进行上移操作,再转回去便成功完成左移操作。