本文主要是介绍C语言实现黑客帝国代码雨,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
🚀欢迎互三👉:程序猿方梓燚 💎💎
🚀关注博主,后期持续更新系列文章
🚀如果有错误感谢请大家批评指出,及时修改
🚀感谢大家点赞👍收藏⭐评论✍
引言
在编程的世界里,我们常常追求创造出令人惊叹的视觉效果和交互体验。今天,我们将深入探索一段用 C语言编写的代码,它实现了一个令人仿佛置身于《黑客帝国》电影中的矩阵效果。
想象一下,你的屏幕上如瀑布般落下一串串神秘的绿色字符,仿佛在诉说着未知的密码。这段代码不仅是技术的展示,更是创造力与编程艺术的结合。对于
C/C++零基础的小白来说,这将是一次充满挑战和惊喜的学习之旅。
通过剖析这段代码,我们将逐步揭开图形界面编程的神秘面纱,了解如何利用 Windows API
实现动态的视觉效果,掌握结构体的运用、消息处理机制以及随机数生成等关键技术。无论你是对编程充满好奇的初学者,还是渴望拓展技能的开发者,这段代码都将为你打开一扇通往新领域的大门。
让我们一同踏上这段代码探索之旅,感受编程的魅力与无限可能。
效果如下:
一、代码概述
这段代码是用 C 语言编写的一个模拟矩阵效果的程序。在屏幕上会显示类似电影《黑客帝国》中的绿色字符流不断下落的效果。用户可以通过右键点击暂停或继续效果,也可以通过按下按键或左键点击退出程序。
二、代码结构分析
一、包含头文件和定义常量部分
#include <windows.h>#define ID_TIMER 1#define STRMAXLEN 25
#define STRMINLEN 8
1.这段代码的开头部分引入了<windows.h>
头文件,这是进行 Windows
编程所必需的。它包含了大量用于创建窗口、处理消息、进行图形绘制等功能的函数和结构体定义。
2.接着,定义了两个常量。ID_TIMER
被设置为1,这个常量将在后续的代码中作为定时器的唯一标识。定时器在程序中用于定期触发特定的操作,比如更新屏幕上的字符列。
3. STRMAXLEN
和STRMINLEN
分别定义为 25 和 8,它们将在后续用于控制字符列中字符串的长度范围。这意味着生成的字符列的长度将在 8 到 25 个字符之间随机确定。
二、定义结构体部分
typedef struct tagCharChain {struct tagCharChain *prev;char ch;struct tagCharChain *next;
} CharChain, *pCharChain;typedef struct tagCharColumn {CharChain *head, *current, *point;int x, y, iStrLen;int iStopTimes, iMustStopTimes;
} CharColumn, *pCharColumn;
1.这里定义了两个重要的结构体。CharChain
结构体代表字符链表中的一个节点。它包含三个成员:prev
是一个指针,指向前一个CharChain
节点;ch
是一个字符,用于存储实际的字符值;next
也是一个指针,指向后一个CharChain
节点。通过这种方式,可以将多个CharChain
节点连接成一个链表,从而实现对一系列字符的存储和管理。
2.CharColumn
结构体代表一个字符列。它包含了多个成员:head
、current
和point
都是CharChain
类型的指针,分别用于指向字符链表的头节点、当前节点和遍历指针;x和y是整数,分别表示字符列在屏幕上的横坐标和纵坐标;iStrLen
是整数,代表字符列中字符串的长度;iStopTimes
和iMustStopTimes
也是整数,用于控制字符列的暂停和继续状态。
三、主函数部分
int main(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {static TCHAR szAppName[] = TEXT("matrix");HWND hwnd;MSG msg;WNDCLASS wndclass;wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = WndProc;wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInstance;wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);wndclass.lpszMenuName = NULL;wndclass.lpszClassName = szAppName;if (!RegisterClass(&wndclass)) {MessageBox(NULL, TEXT("此程序必须运行在 NT 下!"), szAppName, MB_ICONERROR);return 0;}hwnd = CreateWindow(szAppName, NULL, WS_DLGFRAME | WS_THICKFRAME | WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hInstance, NULL);ShowWindow(hwnd, SW_SHOWMAXIMIZED);UpdateWindow(hwnd);ShowCursor(FALSE);srand((int)GetCurrentTime());while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}ShowCursor(TRUE);return msg.wParam;
}
1.主函数是程序的入口点。首先,定义了一些变量,包括应用程序实例句柄hInstance
、前一个应用程序实例句柄hPrevInstance
、命令行参数szCmdLine
和窗口显示参数iCmdShow。此外,还定义了窗口句柄hwnd
、消息结构体msg
和窗口类结构体wndclass
。
2.接着,设置窗口类的各种属性。wndclass.style
被设置为CS_HREDRAW |CS_VREDRAW
,这表示当窗口的水平或垂直大小发生改变时,窗口将自动重绘。wndclass.lpfnWndProc
被设置为WndProc
,这是一个函数指针,指向窗口过程函数,用于处理窗口的各种消息。wndclass.hInstance
被设hInstance
,表示当前应用程序的实例句柄。wndclass.hIcon
和wndclass.hCursor
分别用于设置窗口的图标和光标为默认值。wndclass.hbrBackground
被设置为黑色画刷,这将使窗口的背景为黑wndclass.lpszMenuName
被设置为NULL
,表示窗口没有菜单。wndclass.lpszClassName
被设置为szAppName
,即窗口类的名称。
3.然后,使用RegisterClass(&wndclass)
函数注册窗口类。如果注册失败,将弹出一个错误消息框,并返回 0 表示程序退出。
4.接下来,使用CreateWindow
函数创建窗口。这个函数的参数包括窗口类名、窗口标题、窗口样式、窗口的初始位置和大小、菜单句柄、父窗口句柄、应用程序实例句柄等。这里创建了一个具有对话框框架、粗边框和弹出窗口样式的窗口,窗口的大小设置为屏幕大小。
5.ShowWindow(hwnd, SW_SHOWMAXIMIZED)
用于显示窗口并最大化。UpdateWindow(hwnd)
用于触发一个WM_PAINT
消息,使窗口绘制自身。ShowCursor(FALSE)
用于隐藏光标。
6.然后,使用srand((int)GetCurrentTime())
设置随机数种子。这将确保每次运行程序时生成的随机数序列不同。
7.最后,进入一个消息循环。GetMessage(&msg, NULL, 0, 0)
用于从消息队列中获取消息。如果有消息,将消息进行翻译和分发处理。TranslateMessage(&msg)
用于将虚拟键消息转换为字符消息。DispatchMessage(&msg)
将消息发送给窗口过程函数进行处理。当接收到退出消息时,循环结束。最后,显示光标并返回消息的参数wParam
。
四、随机字符生成函数部分
char randomChar() {return (rand() % 2)? '1' : '0';
}
1.这个函数用于生成随机的字符'0'
或'1'
。它通过调用rand函数生成一个随机数,然后对 2 取余。如果结果为 0,则返回'0'
;否则返回'1'
。
五、初始化字符列函数部分
int init(CharColumn *cc, int cyScreen, int x) {int j;cc->iStrLen = rand() % (STRMAXLEN - STRMINLEN) + STRMINLEN;cc->x = x + 3;cc->y = rand() % 3? rand() % cyScreen : 0;cc->iMustStopTimes = rand() % 6;cc->iStopTimes = 0;cc->head = cc->current = (pCharChain)calloc(cc->iStrLen, sizeof(CharChain));for (j = 0; j < cc->iStrLen - 1; j++) {cc->current->prev = cc->point;cc->current->ch = '\0';cc->current->next = cc->current + 1;cc->point = cc->current++;}cc->current->prev = cc->point;cc->current->ch = '\0';cc->current->next = cc->head;cc->head->prev = cc->current;cc->current = cc->point = cc->head;cc->head->ch = randomChar();return 0;
}
1.这个函数用于初始化一个字符列。它接受一个字符列结构体指针cc
、屏幕高度cyScreen
和一个整数x
作为参数。
2.首先,生成一个随机数作为字符列的长度,范围在STRMINLEN
到STRMAXLEN
之间。然后,设置字符列的横坐标x为传入的参数x
加上 3,纵坐标y为随机生成的值或 0。接着,生成一个随机数作为字符列必须停止的次数。初始化停止次数为 0。
3.然后,使用calloc
函数分配内存,创建一个长度为cc->iStrLen
的字符链表。通过一个循环,设置链表中每个节点的前后指针,形成一个循环链表。
4.最后,将当前指针、遍历指针和头指针指向链表的头节点,并设置头节点的字符为一个随机生成的字符'0'
或'1'
。函数返回 0 表示初始化成功。
六、窗口过程函数部分
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {HDC hdc;int i, j, temp, ctn;static HDC hdcMem;HFONT hFont;static HBITMAP hBitmap;static int cxScreen, cyScreen;static int iFontWidth = 10, iFontHeight = 15, iColumnCount;static CharColumn *ccChain;switch (message) {case WM_CREATE:cxScreen = GetSystemMetrics(SM_CXSCREEN);cyScreen = GetSystemMetrics(SM_CYSCREEN);SetTimer(hwnd, ID_TIMER, 10, NULL);hdc = GetDC(hwnd);hdcMem = CreateCompatibleDC(hdc);hBitmap = CreateCompatibleBitmap(hdc, cxScreen, cyScreen);SelectObject(hdcMem, hBitmap);ReleaseDC(hwnd, hdc);hFont = CreateFont(iFontHeight, iFontWidth - 5, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DRAFT_QUALITY, FIXED_PITCH | FF_SWISS, TEXT("Fixedsys"));SelectObject(hdcMem, hFont);DeleteObject(hFont);SetBkMode(hdcMem, TRANSPARENT);iColumnCount = cxScreen / (iFontWidth * 3 / 2);ccChain = (pCharColumn)calloc(iColumnCount, sizeof(CharColumn));for (i = 0; i < iColumnCount; i++) {init(ccChain + i, cyScreen, (iFontWidth * 3 / 2) * i);}return 0;case WM_TIMER:hdc = GetDC(hwnd);PatBlt(hdcMem, 0, 0, cxScreen, cyScreen, BLACKNESS);for (i = 0; i < iColumnCount; i++) {ctn = (ccChain + i)->iStopTimes++ > (ccChain + i)->iMustStopTimes;(ccChain + i)->point = (ccChain + i)->head;SetTextColor(hdcMem, RGB(255, 255, 255));TextOut(hdcMem, (ccChain + i)->x, (ccChain + i)->y, &((ccChain + i)->point->ch), 1);j = (ccChain + i)->y;(ccChain + i)->point = (ccChain + i)->point->next;temp = 0;while ((ccChain + i)->point!= (ccChain + i)->head && (ccChain + i)->point->ch) {SetTextColor(hdcMem, RGB(0, 255 - (255 * (temp++) / (ccChain + i)->iStrLen), 0));TextOut(hdcMem, (ccChain + i)->x, j -= iFontHeight, &((ccChain + i)->point->ch), 1);(ccChain + i)->point = (ccChain + i)->point->next;}if (ctn)(ccChain + i)->iStopTimes = 0;else continue;(ccChain + i)->y += iFontHeight;if ((ccChain + i)->y - (ccChain + i)->iStrLen * iFontHeight > cyScreen) {free((ccChain + i)->current);init(ccChain + i, cyScreen, (iFontWidth * 3 / 2) * i);}(ccChain + i)->head = (ccChain + i)->head->prev;(ccChain + i)->head->ch = randomChar();}BitBlt(hdc, 0, 0, cxScreen, cyScreen, hdcMem, 0, 0, SRCCOPY);ReleaseDC(hwnd, hdc);return 0;case WM_RBUTTONDOWN:KillTimer(hwnd, ID_TIMER);return 0;case WM_RBUTTONUP:SetTimer(hwnd, ID_TIMER, 10, NULL);return 0;case WM_KEYDOWN:case WM_LBUTTONDOWN:case WM_DESTROY:KillTimer(hwnd, ID_TIMER);DeleteObject(hBitmap);DeleteDC(hdcMem);for (i = 0; i < iColumnCount; i++) {free((ccChain + i)->current);}free(ccChain);PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, message, wParam, lParam);
}
1.这个函数是窗口的消息处理函数。根据不同的消息类型进行不同的处理。
当接收到WM_CREATE消息时,获取屏幕的宽度和高度,设置一个定时器,创建内存设备上下文、位图和字体,设置背景模式为透明,计算字符列的数量,分配内存创建字符列数组,并初始化每个字符列。
2.当接收到WM_TIMER消息时,获取窗口的设备上下文,用黑色填充内存设备上下文,遍历每个字符列,根据停止状态和位置更新字符的显示,将内存设备上下文的内容复制到窗口设备上下文。
3.当接收到右键按下消息WM_RBUTTONDOWN时,停止定时器。当接收到右键抬起消息WM_RBUTTONUP时,重新启动定时器。
4.当接收到按键按下消息WM_KEYDOWN、左键按下消息WM_LBUTTONDOWN或窗口销毁消息WM_DESTROY时,停止定时器,删除位图、内存设备上下文,释放每个字符列的链表内存和字符列数组内存,发送退出消息。
5.如果接收到的消息不是上述特定的消息类型,则调用DefWindowProc函数进行默认处理。
三、完整代码
#include <windows.h>// 定义定时器的 ID
#define ID_TIMER 1// 定义字符串的最大长度和最小长度
#define STRMAXLEN 25
#define STRMINLEN 8// 声明窗口过程函数
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// 定义字符链表结构体
typedef struct tagCharChain {struct tagCharChain *prev; // 指向前一个字符节点的指针char ch; // 字符struct tagCharChain *next; // 指向后一个字符节点的指针
} CharChain, *pCharChain;// 定义字符列结构体
typedef struct tagCharColumn {CharChain *head, *current, *point; // 分别指向链表头、当前节点、遍历指针int x, y, iStrLen; // x、y 坐标和字符串长度int iStopTimes, iMustStopTimes; // 停止次数和必须停止的次数
} CharColumn, *pCharColumn;// 主函数入口
int main(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {// 窗口类名static TCHAR szAppName[] = TEXT("matrix");HWND hwnd; // 窗口句柄MSG msg; // 消息结构体WNDCLASS wndclass; // 窗口类结构体// 设置窗口类的风格为水平和垂直重绘wndclass.style = CS_HREDRAW | CS_VREDRAW;// 设置窗口过程函数为 WndProcwndclass.lpfnWndProc = WndProc;// 类的额外字节数为 0wndclass.cbClsExtra = 0;// 窗口的额外字节数为 0wndclass.cbWndExtra = 0;// 应用程序实例句柄wndclass.hInstance = hInstance;// 加载默认图标wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);// 加载默认光标wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);// 设置窗口背景为黑色画刷wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);// 菜单名为 NULL,表示没有菜单wndclass.lpszMenuName = NULL;// 窗口类名wndclass.lpszClassName = szAppName;// 注册窗口类if (!RegisterClass(&wndclass)) {// 如果注册失败,弹出错误消息框并返回 0MessageBox(NULL, TEXT("此程序必须运行在 NT 下!"), szAppName, MB_ICONERROR);return 0;}// 创建窗口hwnd = CreateWindow(szAppName, NULL, WS_DLGFRAME | WS_THICKFRAME | WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hInstance, NULL);// 显示窗口并更新窗口ShowWindow(hwnd, SW_SHOWMAXIMIZED);UpdateWindow(hwnd);// 隐藏光标ShowCursor(FALSE);// 使用当前时间作为随机数种子srand((int)GetCurrentTime());// 消息循环while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}// 显示光标ShowCursor(TRUE);return msg.wParam;
}// 生成随机字符,0 或 1
char randomChar() {return (rand() % 2) ? '1' : '0';
}// 初始化字符列
int init(CharColumn *cc, int cyScreen, int x) {int j;// 随机生成字符串长度cc->iStrLen = rand() % (STRMAXLEN - STRMINLEN) + STRMINLEN;// 设置 x 坐标cc->x = x + 3;// 随机设置 y 坐标或为 0cc->y = rand() % 3 ? rand() % cyScreen : 0;// 随机生成必须停止的次数cc->iMustStopTimes = rand() % 6;// 初始化停止次数为 0cc->iStopTimes = 0;// 分配内存创建字符链表cc->head = cc->current = (pCharChain)calloc(cc->iStrLen, sizeof(CharChain));for (j = 0; j < cc->iStrLen - 1; j++) {// 设置链表节点的前后指针cc->current->prev = cc->point;cc->current->ch = '\0';cc->current->next = cc->current + 1;cc->point = cc->current++;}// 设置最后一个节点的指针cc->current->prev = cc->point;cc->current->ch = '\0';cc->current->next = cc->head;cc->head->prev = cc->current;cc->current = cc->point = cc->head;// 设置链表头节点的字符为随机字符cc->head->ch = randomChar();return 0;
}// 窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {HDC hdc; // 设备上下文句柄int i, j, temp, ctn;static HDC hdcMem; // 内存设备上下文句柄HFONT hFont; // 字体句柄static HBITMAP hBitmap; // 位图句柄static int cxScreen, cyScreen; // 屏幕宽度和高度static int iFontWidth = 10, iFontHeight = 15, iColumnCount; // 字体宽度、高度和列数static CharColumn *ccChain; // 字符列指针switch (message) {case WM_CREATE:// 获取屏幕宽度和高度cxScreen = GetSystemMetrics(SM_CXSCREEN);cyScreen = GetSystemMetrics(SM_CYSCREEN);// 设置定时器,ID 为 ID_TIMER,时间间隔为 10 毫秒SetTimer(hwnd, ID_TIMER, 10, NULL);// 获取窗口设备上下文hdc = GetDC(hwnd);// 创建与窗口设备上下文兼容的内存设备上下文hdcMem = CreateCompatibleDC(hdc);// 创建与窗口兼容的位图hBitmap = CreateCompatibleBitmap(hdc, cxScreen, cyScreen);// 选择位图到内存设备上下文SelectObject(hdcMem, hBitmap);// 释放窗口设备上下文ReleaseDC(hwnd, hdc);// 创建字体hFont = CreateFont(iFontHeight, iFontWidth - 5, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DRAFT_QUALITY, FIXED_PITCH | FF_SWISS, TEXT("Fixedsys"));// 选择字体到内存设备上下文SelectObject(hdcMem, hFont);// 删除字体句柄DeleteObject(hFont);// 设置背景模式为透明SetBkMode(hdcMem, TRANSPARENT);// 计算列数iColumnCount = cxScreen / (iFontWidth * 3 / 2);// 分配内存创建字符列数组ccChain = (pCharColumn)calloc(iColumnCount, sizeof(CharColumn));for (i = 0; i < iColumnCount; i++) {// 初始化每个字符列init(ccChain + i, cyScreen, (iFontWidth * 3 / 2) * i);}return 0;case WM_TIMER:// 获取窗口设备上下文hdc = GetDC(hwnd);// 用黑色填充内存设备上下文PatBlt(hdcMem, 0, 0, cxScreen, cyScreen, BLACKNESS);for (i = 0; i < iColumnCount; i++) {// 判断是否应该停止ctn = (ccChain + i)->iStopTimes++ > (ccChain + i)->iMustStopTimes;// 设置遍历指针为链表头(ccChain + i)->point = (ccChain + i)->head;// 设置文本颜色为白色SetTextColor(hdcMem, RGB(255, 255, 255));// 在内存设备上下文输出一个字符TextOut(hdcMem, (ccChain + i)->x, (ccChain + i)->y, &((ccChain + i)->point->ch), 1);j = (ccChain + i)->y;// 移动遍历指针到下一个节点(ccChain + i)->point = (ccChain + i)->point->next;temp = 0;while ((ccChain + i)->point != (ccChain + i)->head && (ccChain + i)->point->ch) {// 设置文本颜色为渐变绿色SetTextColor(hdcMem, RGB(0, 255 - (255 * (temp++) / (ccChain + i)->iStrLen), 0));// 在内存设备上下文输出一个字符TextOut(hdcMem, (ccChain + i)->x, j -= iFontHeight, &((ccChain + i)->point->ch), 1);// 移动遍历指针到下一个节点(ccChain + i)->point = (ccChain + i)->point->next;}if (ctn)// 如果应该停止,重置停止次数(ccChain + i)->iStopTimes = 0;else continue;// 更新 y 坐标(ccChain + i)->y += iFontHeight;if ((ccChain + i)->y - (ccChain + i)->iStrLen * iFontHeight > cyScreen) {// 如果超出屏幕,释放链表内存并重新初始化free((ccChain + i)->current);init(ccChain + i, cyScreen, (iFontWidth * 3 / 2) * i);}// 链表头指针移动到前一个节点(ccChain + i)->head = (ccChain + i)->head->prev;// 设置新的随机字符到链表头(ccChain + i)->head->ch = randomChar();}// 将内存设备上下文的内容复制到窗口设备上下文BitBlt(hdc, 0, 0, cxScreen, cyScreen, hdcMem, 0, 0, SRCCOPY);// 释放窗口设备上下文ReleaseDC(hwnd, hdc);return 0;case WM_RBUTTONDOWN:// 右键按下,停止定时器KillTimer(hwnd, ID_TIMER);return 0;case WM_RBUTTONUP:// 右键抬起,重新启动定时器SetTimer(hwnd, ID_TIMER, 10, NULL);return 0;case WM_KEYDOWN:case WM_LBUTTONDOWN:case WM_DESTROY:// 按键按下、左键按下或窗口销毁时,停止定时器KillTimer(hwnd, ID_TIMER);// 删除位图DeleteObject(hBitmap);// 删除内存设备上下文DeleteDC(hdcMem);for (i = 0; i < iColumnCount; i++) {// 释放每个字符列的链表内存free((ccChain + i)->current);}// 释放字符列数组内存free(ccChain);// 发送退出消息PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, message, wParam, lParam);
}
四、总结
总体而言,这段代码通过巧妙地运用数据结构、消息处理、图形绘制和随机数生成等技术,创造了一个具有吸引力的动态可视化效果,为 C 语言在图形界面编程方面提供了一个很好的示例。
这篇关于C语言实现黑客帝国代码雨的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!