如何在C语言四子棋中实现30秒走步倒计时及5秒提醒功能?
四子棋30秒走步限制+倒计时提醒实现方案
核心思路
利用信号机制实现定时提醒与超时判定,结合IO多路复用解决scanf()阻塞导致的定时器失效问题。
具体实现步骤
信号与定时器配置
- 注册
SIGALRM信号处理函数,用于响应定时事件。 - 放弃单一的
alarm(30),分阶段触发:先设置25秒定时器触发“剩余5秒”提醒,再设置5秒定时器触发超时判定。 - 用
volatile修饰全局标志(如warn_triggered、timeout_triggered),确保信号处理函数的修改能被主循环实时感知。
- 注册
解决输入阻塞问题
- 不能直接用阻塞式的
scanf(),否则定时器触发时无法中断输入流程。推荐用select()实现带超时的输入监听:- 每次循环中用
select()等待标准输入可读,同时设置剩余时间为超时阈值。 - 若
select()返回0,说明超时;返回大于0则读取输入并校验有效性。
- 每次循环中用
- 不能直接用阻塞式的
信号处理函数的极简设计
- 信号处理函数内只做基础操作:修改全局标志、打印提示,避免调用非重入函数(如复杂IO操作),保证信号处理的安全性。
核心代码片段示例
#include <signal.h> #include <unistd.h> #include <stdio.h> #include <sys/select.h> // 全局标志,用volatile确保跨信号的可见性 volatile int warn_triggered = 0; volatile int timeout_triggered = 0; void sigalrm_handler(int sig) { if (!warn_triggered) { printf("\n⚠️ 剩余5秒,请尽快走步!\n"); warn_triggered = 1; alarm(5); // 触发最后5秒的超时定时器 } else { printf("\n⏰ 时间耗尽!\n"); timeout_triggered = 1; } } int main() { signal(SIGALRM, sigalrm_handler); int move; while(1) { warn_triggered = 0; timeout_triggered = 0; alarm(25); // 25秒后触发提醒 printMat(); do { // 检查超时标志,直接终止输入循环 if (timeout_triggered) break; printf("\nMake a move: "); fflush(stdout); // 确保提示语及时输出 fd_set read_fds; struct timeval tv; FD_ZERO(&read_fds); FD_SET(STDIN_FILENO, &read_fds); // 动态设置剩余超时时间 tv.tv_sec = warn_triggered ? 5 : 25; tv.tv_usec = 0; int select_ret = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &tv); if (select_ret == 0) { // select超时,触发超时标志 timeout_triggered = 1; break; } else if (select_ret > 0) { // 读取输入并校验有效性 scanf("%d", &move); if (!isNotValid(move)) break; printf("无效走步,请重新输入!\n"); } } while(1); alarm(0); // 取消未触发的定时器 // 处理超时逻辑 if (timeout_triggered) { printf("当前玩家超时,判负!\n"); SwapPlayer(); continue; } // 正常走步逻辑 move(move, player_symbol); SwapPlayer(); } return 0; }
关键注意事项
select()的超时时间需要根据是否触发提醒动态调整,避免时间计算偏差。- 信号处理函数中尽量只调用
printf这类简单可重入函数,减少潜在风险。 - 每次回合开始前必须重置全局标志和定时器,避免上一回合的状态干扰当前回合。
内容的提问来源于stack exchange,提问作者Enange
相关产品推荐
相关产品推荐

