想在 DOS 時代的 Turbo C 環境下,親手繪製一個擁有模擬指針與數字顯示的電子時鐘嗎?本文將帶你完整走過一個經典 graphics.h 專案的生命週期:從繪製表盤、刻度、指針,到處理鍵盤輸入調整時間,最後實現秒針流暢跳動的動畫迴圈。文末附上完整可編譯的原始碼,適合對繪圖程式設計、即時系統邏輯有興趣的學習者參考。
程式執行後會彈出一個 640×480 的圖形視窗,左側為操作說明區,右側為紅藍配色的時鐘表盤,下方同步顯示數字時間。按鍵操作如下:
整支程式約 300 行,採用「主程式負責初始化與繪製靜態介面,ClockHandle() 負責動畫迴圈與事件處理」的雙層架構。關鍵全域變數集中管理表盤幾何參數(圓心、半徑)與時間狀態,各功能函式職責單一,便於維護。
main())floodfill() 填滿黑色表盤背景。for 迴圈配合三角函數 sin()/cos(),以 6° 間距繪製 60 條分刻度(白色細點),每 5 格加粗紅色為時刻度。outtextxy() 寫入標題、作者、操作提示與裝飾字串。ClockHandle()採用 XOR 繪圖模式(setwritemode(1)) 實現「畫一次、擦一次」的無閃爍指針動畫:
gettime())初始化時、分、秒指針角度。for(;;) 無限迴圈,每圈只在秒數變化時重繪秒針;分、時指針僅在對應數值變動時更新。while 以 bioskey(1) 非阻塞檢查鍵盤,若有按鍵立即呼叫 KeyHandle() 處理時間調整與光標移動。DigitalClock() 先用黑色長條遮蓋舊值,再印新值。Count 變數(1=時、2=分、3=秒)追蹤光標位置,Up_Key()/Down_Key() 直接修改 struct time Time[1] 並呼叫 settime() 寫回硬體 RTC,達成「改時間即改系統時間」的真實感。
x = Center_x + Radius * sin(angle * PI / 180);
y = Center_y - Radius * cos(angle * PI / 180);
注意 Y 軸向下為正,故 cosine 取負。
setwritemode(1) 將像素與背景做 XOR,同一座標畫兩次即還原背景,無需重繪整個表盤,極大降低 CPU 佔用。
DigitalClock() 以 bar() 清除固定寬度區域,再 sprintf() 輸出,避免位數變化(如 9→10)殘留舊字元。
/*-----------------------------電子時鐘源代碼---------------------------------*/
/*------------------------------預處理----------------------------------------*/
#include <graphics.h>
#include <math.h>
#include <dos.h>
#include <stdio.h>
/*-----------------------------宏定義-----------------------------------------*/
#define PI 3.1415926
#define Up 0x4800
#define Down 0x5000
#define Tab 0x0F09
#define Esc 0x011B
/*-----------------------------定義全局變量-----------------------------------*/
double Hour, Minute, Second;
int Radius = 130, Center_x = 300, Center_y = 265;
double Hour_x, Hour_y, Minute_x, Minute_y, Second_x, Second_y;
struct time Time[1];
/*-----------------------------函數的聲明-------------------------------------*/
void ClockHandle();
int DigitalHour(double Hour);
int DigitalMinute(double Minute);
int DigitalSecond(double Second);
void DigitalClock(int x, int y, int clock);
int Up_Key(int Count);
int Down_Key(int Count);
void ClearCursor(int Count);
void DrawCursor(int Count);
int KeyHandle(int Key, int Count);
/*----------------------------主函數------------------------------------------*/
main()
{
/*--------------繪底板----------------------*/
int Driver, Mode;
int i, j;
Driver = DETECT;
initgraph(&Driver, &Mode, "");
setcolor(RED);
line(300, 0, 5, 120);
line(300, 0, 595, 120);
line(5, 120, 595, 120);
setfillstyle(SOLID_FILL, RED);
floodfill(300, 100, RED);
setcolor(BLUE);
line(65, 120, 535, 120);
line(65, 120, 65, 470);
line(535, 120, 535, 470);
line(65, 470, 535, 470);
line(125, 120, 125, 470);
line(475, 120, 475, 470);
line(125, 410, 475, 410);
setfillstyle(SOLID_FILL, BLUE);
floodfill(80, 130, BLUE);
floodfill(500, 130, BLUE);
setfillstyle(SOLID_FILL, BLACK);
floodfill(300, 450, BLUE);
/*-------------繪時間間隔符-----------------*/
for (i = 275; i <= 305; i += 30)
for (j = 434; j <= 442; j += 8) {
setcolor(WHITE);
setlinestyle(0, 0, 3);
circle(i, j, 1);
}
/*------------繪制光標----------------------*/
setcolor(WHITE);
line(270, 430, 270, 445);
/*-------------繪制表軸---------------------*/
setlinestyle(0, 0, 3);
circle(Center_x, Center_y, 1);
/*-------------繪制表框---------------------*/
setlinestyle(0, 0, 1);
circle(Center_x, Center_y, Radius + 3);
/*--------------繪制分針刻度----------------*/
for (i = 0, Minute = 0; i <= 59; i++, Minute++) {
Minute_x = Center_x + Radius * sin(Minute / 30.0 * PI);
Minute_y = Center_y - Radius * cos(Minute / 30.0 * PI);
setlinestyle(0, 0, 1);
circle((int)Minute_x, (int)Minute_y, 1);
}
/*--------------繪制時針刻度----------------*/
setcolor(RED);
for (i = 0, Hour = 0, Minute = 0; i <= 11; i++, Hour++) {
Hour_x = Center_x + Radius * sin((Hour * 60 + Minute) / 360.0 * PI);
Hour_y = Center_y - Radius * cos((Hour * 60 + Minute) / 360.0 * PI);
setlinestyle(0, 0, 3);
circle((int)Hour_x, (int)Hour_y, 1);
}
/*--------------添加文本--------------------*/
setcolor(GREEN);
outtextxy(283, 50, "CLOCK");
outtextxy(320, 110, "Designed by Zhang Cheng Jin");
outtextxy(80, 130, "Help");
outtextxy(70, 150, "Up:");
outtextxy(78, 168, "Time++");
outtextxy(70, 216, "Down:");
outtextxy(78, 234, "Time--");
outtextxy(70, 286, "Tab:");
outtextxy(78, 304, "Move");
outtextxy(70, 356, "Esc:");
outtextxy(78, 374, "Exit");
outtextxy(500, 190, "No");
outtextxy(490, 215, "time");
outtextxy(490, 240, "like");
outtextxy(495, 265, "the");
outtextxy(480, 290, "present");
outtextxy(500, 315, "!");
/*----------調用函數-----------------------*/
ClockHandle();
/*-----------關閉圖形----------------------*/
closegraph();
return 0;
}
/*---------------------------時鐘動畫處理函數---------------------------------*/
void ClockHandle()
{
int Key = 0, Count = 2;
setcolor(WHITE);
/*----------------獲取系統時間-------------*/
gettime(Time);
Hour = Time[0].ti_hour;
Minute = Time[0].ti_min;
/*----------------繪制分針-----------------*/
Minute_x = Center_x + 100 * sin(Minute / 30.0 * PI);
Minute_y = Center_y - 100 * cos(Minute / 30.0 * PI);
line(Center_x, Center_y, (int)Minute_x, (int)Minute_y);
/*----------------繪制時針-----------------*/
Hour_x = Center_x + 80 * sin((Hour * 60 + Minute) / 360.0 * PI);
Hour_y = Center_y - 80 * cos((Hour * 60 + Minute) / 360.0 * PI);
line(Center_x, Center_y, (int)Hour_x, (int)Hour_y);
/*----------------繪制數字時鐘-------------*/
DigitalClock(255, 435, DigitalHour(Hour));
DigitalClock(285, 435, DigitalMinute(Minute));
setwritemode(1); // XOR 模式
for (Count = 2; Key != Esc; ) {
/*----------------繪制秒針-----------------*/
setcolor(RED);
gettime(Time);
Hour = Time[0].ti_hour;
Minute = Time[0].ti_min;
Second = Time[0].ti_sec;
Second_x = Center_x + 120 * sin(Second / 30.0 * PI);
Second_y = Center_y - 120 * cos(Second / 30.0 * PI);
line(Center_x, Center_y, (int)Second_x, (int)Second_y);
/*-----------------------------------------*/
while (Hour == Time[0].ti_hour &&
Minute == Time[0].ti_min &&
Second == Time[0].ti_sec) {
gettime(Time);
if (bioskey(1) != 0) {
Key = bioskey(0);
Count = KeyHandle(Key, Count);
if (Count == 5) Count = 1;
}
}
/*----------繪制數字時鐘的秒---------------*/
setcolor(WHITE);
DigitalClock(315, 435, DigitalSecond(Second) + 1);
/*----------清除前一個秒針的圖形-----------*/
setcolor(RED);
Second_x = Center_x + 120 * sin(Second / 30.0 * PI);
Second_y = Center_y - 120 * cos(Second / 30.0 * PI);
line(Center_x, Center_y, (int)Second_x, (int)Second_y);
setcolor(WHITE);
/*---------對分針變化的處理----------------*/
if (Minute != Time[0].ti_min) {
Minute_x = Center_x + 100 * sin(Minute / 30.0 * PI);
Minute_y = Center_y - 100 * cos(Minute / 30.0 * PI);
line(Center_x, Center_y, (int)Minute_x, (int)Minute_y);
Minute = Time[0].ti_min;
DigitalClock(285, 435, DigitalMinute(Minute));
Minute_x = Center_x + 100 * sin(Minute / 30.0 * PI);
Minute_y = Center_y - 100 * cos(Minute / 30.0 * PI);
line(Center_x, Center_y, (int)Minute_x, (int)Minute_y);
}
/*----------對時針變化的處理---------------*/
if (Hour != Time[0].ti_hour) {
Hour_x = Center_x + 80 * sin((Hour * 60 + Minute) / 360.0 * PI);
Hour_y = Center_y - 80 * cos((Hour * 60 + Minute) / 360.0 * PI);
line(Center_x, Center_y, (int)Hour_x, (int)Hour_y);
Hour = Time[0].ti_hour;
DigitalClock(255, 435, DigitalHour(Hour));
Hour_x = Center_x + 80 * sin((Hour * 60 + Minute) / 360.0 * PI);
Hour_y = Center_y - 80 * cos((Hour * 60 + Minute) / 360.0 * PI);
line(Center_x, Center_y, (int)Hour_x, (int)Hour_y);
}
}
}
/*------------------------數字時鐘模塊----------------------------------------*/
int DigitalHour(double Hour) { return (int)Hour; }
int DigitalMinute(double Minute) { return (int)Minute; }
int DigitalSecond(double Second) { return (int)Second; }
void DigitalClock(int x, int y, int clock)
{
char buffer[10];
setfillstyle(0, 0); // 黑色填充
bar(x, y, x + 14, 449);
if (clock == 60) clock = 0;
sprintf(buffer, "%02d", clock);
outtextxy(x, y, buffer);
}
/*----------------------按鍵處理函數------------------------------------------*/
int KeyHandle(int Key, int Count)
{
switch (Key) {
case Up: Up_Key(Count); break;
case Down: Down_Key(Count); break;
case Tab:
ClearCursor(Count);
Count++;
if (Count == 4) Count = 1;
DrawCursor(Count);
break;
}
return Count;
}
/*--------------------向上方向鍵函數------------------------------------------*/
int Up_Key(int Count)
{
gettime(Time);
switch (Count) {
case 1:
Time[0].ti_hour = (Time[0].ti_hour + 23) % 24;
settime(Time);
break;
case 2:
Time[0].ti_min = (Time[0].ti_min + 59) % 60;
settime(Time);
break;
case 3:
Time[0].ti_sec = (Time[0].ti_sec + 59) % 60;
settime(Time);
break;
}
return 0;
}
/*--------------------向下方向鍵函數------------------------------------------*/
int Down_Key(int Count)
{
gettime(Time);
switch (Count) {
case 1:
Time[0].ti_hour = (Time[0].ti_hour + 1) % 24;
settime(Time);
break;
case 2:
Time[0].ti_min = (Time[0].ti_min + 1) % 60;
settime(Time);
break;
case 3:
Time[0].ti_sec = (Time[0].ti_sec + 1) % 60;
settime(Time);
break;
}
return 0;
}
/*---------------------光標處理函數-------------------------------------------*/
void ClearCursor(int Count)
{
setcolor(BLACK); // 用背景色抹除
switch (Count) {
case 1: line(270, 430, 270, 445); break;
case 2: line(300, 430, 300, 445); break;
case 3: line(330, 430, 330, 445); break;
}
}
void DrawCursor(int Count)
{
setcolor(WHITE);
switch (Count) {
case 1: line(270, 430, 270, 445); break;
case 2: line(300, 430, 300, 445); break;
case 3: line(330, 430, 330, 445); break;
}
}
EGAVGA.BGI 與 TRIP.CHR 在執行目錄或 initgraph() 指定路徑graphics.h 移植至 SDL2 / SFML / Windows GDI,體驗現代繪圖管線。sound() 蜂鳴器。BitBlt 一次貼上,完全消除閃爍。這支程式雖屬教學範例,卻涵蓋了「圖形初始化、幾何繪製、即時迴圈、輸入處理、硬體時間存取」等系統程式設計核心要素,是從傳統 DOS 繪圖邁向現代遊戲迴圈架構的絕佳垫腳石。