Linux下C语言开发井字棋:如何让多子进程在独立终端运行
解决方案思路
核心是让新启动的终端重新执行当前程序的特定分支,而非外部命令。通过命令行参数区分进程角色(主进程/玩家1/玩家2/显示窗口),配合共享内存+消息队列实现进程间通信与同步。
关键步骤
- 程序启动时解析命令行参数,判断自身角色:
- 无参数:作为主进程,负责创建共享内存、消息队列,fork并启动三个子终端进程。
- 带
player1/player2/display参数:直接执行对应角色的业务逻辑。
- 子进程通过
system()启动终端(如xterm -e ./tic_tac_toe player1),让新终端运行当前程序的玩家/显示分支。 - 用共享内存存储棋盘状态,消息队列传递落子请求、更新通知,信号量保证共享内存的互斥访问。
代码实现示例
1. 头文件与全局定义
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/msg.h> #include <sys/sem.h> #include <string.h> #include <wait.h> // 棋盘大小 #define SIZE 3 // 消息类型 #define MSG_PLAYER1 1 #define MSG_PLAYER2 2 #define MSG_DISPLAY 3 #define MSG_END 4 // 共享内存的棋盘结构 typedef struct { char grid[SIZE][SIZE]; int turn; // 0: player1, 1: player2 int game_over; // 0: 进行中, 1: 结束 } Board; // 消息队列结构 typedef struct { long mtype; int row; int col; char winner; // 'X'/'O'/'D'(平局) } Msg; // 信号量操作结构体 struct sembuf sem_op;
2. 主进程逻辑
主进程负责初始化IPC资源,启动三个子终端进程,最后清理资源:
void player1_logic(); void player2_logic(); void display_logic(); int check_winner(Board *board); int main(int argc, char *argv[]) { // 如果是子角色进程,直接进入对应逻辑 if (argc > 1) { if (strcmp(argv[1], "player1") == 0) { player1_logic(); } else if (strcmp(argv[1], "player2") == 0) { player2_logic(); } else if (strcmp(argv[1], "display") == 0) { display_logic(); } exit(0); } // 主进程:创建IPC资源 key_t shm_key = ftok(".", 'b'); int shm_id = shmget(shm_key, sizeof(Board), IPC_CREAT | 0666); Board *board = (Board*)shmat(shm_id, NULL, 0); // 初始化棋盘 memset(board->grid, ' ', sizeof(board->grid)); board->turn = 0; board->game_over = 0; // 创建消息队列 key_t msg_key = ftok(".", 'm'); int msg_id = msgget(msg_key, IPC_CREAT | 0666); // 创建信号量(用于共享内存互斥) key_t sem_key = ftok(".", 's'); int sem_id = semget(sem_key, 1, IPC_CREAT | 0666); semctl(sem_id, 0, SETVAL, 1); // 初始值1,互斥锁 // 启动三个子终端进程 if (fork() == 0) { system("xterm -e ./tic_tac_toe player1"); exit(0); } if (fork() == 0) { system("xterm -e ./tic_tac_toe player2"); exit(0); } if (fork() == 0) { system("xterm -e ./tic_tac_toe display"); exit(0); } // 主进程等待所有子进程结束 wait(NULL); wait(NULL); wait(NULL); // 清理IPC资源 shmdt(board); shmctl(shm_id, IPC_RMID, NULL); msgctl(msg_id, IPC_RMID, NULL); semctl(sem_id, 0, IPC_RMID); return 0; }
3. 玩家1逻辑示例
void player1_logic() { key_t shm_key = ftok(".", 'b'); int shm_id = shmget(shm_key, sizeof(Board), 0666); Board *board = (Board*)shmat(shm_id, NULL, 0); key_t msg_key = ftok(".", 'm'); int msg_id = msgget(msg_key, 0666); key_t sem_key = ftok(".", 's'); int sem_id = semget(sem_key, 1, 0666); Msg msg; int row, col; while (1) { // 检查游戏是否结束 sem_op.sem_num = 0; sem_op.sem_op = -1; sem_op.sem_flg = 0; semop(sem_id, &sem_op, 1); if (board->game_over) { sem_op.sem_op = 1; semop(sem_id, &sem_op, 1); break; } sem_op.sem_op = 1; semop(sem_id, &sem_op, 1); // 等待自己的回合 sem_op.sem_op = -1; semop(sem_id, &sem_op, 1); if (board->turn != 0) { sem_op.sem_op = 1; semop(sem_id, &sem_op, 1); sleep(1); continue; } sem_op.sem_op = 1; semop(sem_id, &sem_op, 1); // 输入落子位置 printf("Player 1 (X), enter row (0-2) and column (0-2): "); scanf("%d %d", &row, &col); // 验证合法性并落子 sem_op.sem_op = -1; semop(sem_id, &sem_op, 1); if (row >=0 && row < SIZE && col >=0 && col < SIZE && board->grid[row][col] == ' ') { board->grid[row][col] = 'X'; // 检查胜负 int result = check_winner(board); if (result) { board->game_over = 1; msg.mtype = MSG_DISPLAY; msg.winner = result == 1 ? 'X' : 'D'; msgsnd(msg_id, &msg, sizeof(msg)-sizeof(long), 0); // 通知另一个玩家 msg.mtype = MSG_PLAYER2; msgsnd(msg_id, &msg, sizeof(msg)-sizeof(long), 0); } else { board->turn = 1; // 切换回合 msg.mtype = MSG_DISPLAY; msg.row = row; msg.col = col; msgsnd(msg_id, &msg, sizeof(msg)-sizeof(long), 0); } } else { printf("Invalid move! Try again.\n"); } sem_op.sem_op = 1; semop(sem_id, &sem_op, 1); } shmdt(board); printf("Game over! Player 1 exiting...\n"); sleep(2); }
4. 显示进程逻辑示例
void display_logic() { key_t shm_key = ftok(".", 'b'); int shm_id = shmget(shm_key, sizeof(Board), 0666); Board *board = (Board*)shmat(shm_id, NULL, 0); key_t msg_key = ftok(".", 'm'); int msg_id = msgget(msg_key, 0666); key_t sem_key = ftok(".", 'b'); int sem_id = semget(sem_key, 1, 0666); Msg msg; // 初始显示空棋盘 system("clear"); printf("Tic Tac Toe\n"); for (int i=0; i<SIZE; i++) { for (int j=0; j<SIZE; j++) { printf(" %c ", board->grid[i][j]); if (j < SIZE-1) printf("|"); } printf("\n"); if (i < SIZE-1) printf("---+---+---\n"); } while (1) { msgrcv(msg_id, &msg, sizeof(msg)-sizeof(long), MSG_DISPLAY, 0); // 检查游戏是否结束 sem_op.sem_num = 0; sem_op.sem_op = -1; sem_op.sem_flg = 0; semop(sem_id, &sem_op, 1); if (board->game_over) { system("clear"); printf("Game Over!\n"); if (msg.winner == 'D') { printf("It's a draw!\n"); } else { printf("Player %c wins!\n", msg.winner); } sem_op.sem_op = 1; semop(sem_id, &sem_op, 1); break; } // 更新显示棋盘 system("clear"); printf("Tic Tac Toe\n"); for (int i=0; i<SIZE; i++) { for (int j=0; j<SIZE; j++) { printf(" %c ", board->grid[i][j]); if (j < SIZE-1) printf("|"); } printf("\n"); if (i < SIZE-1) printf("---+---+---\n"); } sem_op.sem_op = 1; semop(sem_id, &sem_op, 1); } shmdt(board); sleep(3); }
5. 胜负判断辅助函数
int check_winner(Board *board) { // 检查行 for (int i=0; i<SIZE; i++) { if (board->grid[i][0] != ' ' && board->grid[i][0] == board->grid[i][1] && board->grid[i][1] == board->grid[i][2]) { return board->grid[i][0] == 'X' ? 1 : 2; } } // 检查列 for (int j=0; j<SIZE; j++) { if (board->grid[0][j] != ' ' && board->grid[0][j] == board->grid[1][j] && board->grid[1][j] == board->grid[2][j]) { return board->grid[0][j] == 'X' ? 1 : 2; } } // 检查对角线 if (board->grid[0][0] != ' ' && board->grid[0][0] == board->grid[1][1] && board->grid[1][1] == board->grid[2][2]) { return board->grid[0][0] == 'X' ? 1 : 2; } if (board->grid[0][2] != ' ' && board->grid[0][2] == board->grid[1][1] && board->grid[1][1] == board->grid[2][0]) { return board->grid[0][2] == 'X' ? 1 : 2; } // 检查平局 int full = 1; for (int i=0; i<SIZE; i++) { for (int j=0; j<SIZE; j++) { if (board->grid[i][j] == ' ') { full = 0; break; } } if (!full) break; } if (full) return 3; // 平局 return 0; // 未分胜负 }
注意事项
- 终端命令:如果
xterm不可用,可替换为gnome-terminal -- ./tic_tac_toe player1或konsole -e ./tic_tac_toe player1,根据桌面环境调整。 - IPC资源清理:若程序异常退出,可通过
ipcs查看残留的共享内存/消息队列/信号量,用ipcrm手动删除。 - 玩家2逻辑可参考玩家1实现,仅需将角色标识改为
O,回合判断改为board->turn == 1。
内容的提问来源于stack exchange,提问作者Belal Qatoum
相关产品推荐
相关产品推荐

