Skip to content

Commit f3976e0

Browse files
committed
fix: 타이머 표시 정밀도 향상 및 서버 측 시간 관리 오류 수정
1 parent ab4c141 commit f3976e0

File tree

5 files changed

+228
-69
lines changed

5 files changed

+228
-69
lines changed

common/config.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ typedef struct {
3232
#define DEFAULT_CONNECTION_TIMEOUT 10
3333

3434
// 기본 게임 설정
35-
#define DEFAULT_GAME_TIME_LIMIT 10 // 10분
35+
#define DEFAULT_GAME_TIME_LIMIT 30 // 초 단위
3636
#define DEFAULT_MAX_CHAT_MESSAGES 50
3737
#define DEFAULT_CHAT_MESSAGE_LENGTH 256
3838

server/handlers/match.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ int handle_match_game_message(int fd, ClientMessage *req) {
8181
match_resp.assigned_team = result.assigned_team;
8282
match_resp.opponent_name = result.opponent_name ? result.opponent_name : "";
8383

84-
// 타이머 정보 추가
85-
match_resp.time_limit_per_player = game->time_limit_per_player;
86-
match_resp.white_time_remaining = game->white_time_remaining;
87-
match_resp.black_time_remaining = game->black_time_remaining;
84+
// 타이머 정보 추가 (밀리초를 초로 변환해서 전송)
85+
match_resp.time_limit_per_player = game->time_limit_per_player / 1000;
86+
match_resp.white_time_remaining = game->white_time_remaining / 1000;
87+
match_resp.black_time_remaining = game->black_time_remaining / 1000;
8888

8989
// 게임 시작 시간 설정
9090
match_resp.game_start_time = malloc(sizeof(Google__Protobuf__Timestamp));
@@ -120,10 +120,10 @@ int handle_match_game_message(int fd, ClientMessage *req) {
120120
opponent_resp.assigned_team = (result.assigned_team == TEAM__TEAM_WHITE) ? TEAM__TEAM_BLACK : TEAM__TEAM_WHITE;
121121
opponent_resp.opponent_name = current_player_name ? current_player_name : "";
122122

123-
// 상대방에게도 타이머 정보 추가
124-
opponent_resp.time_limit_per_player = game->time_limit_per_player;
125-
opponent_resp.white_time_remaining = game->white_time_remaining;
126-
opponent_resp.black_time_remaining = game->black_time_remaining;
123+
// 상대방에게도 타이머 정보 추가 (밀리초를 초로 변환해서 전송)
124+
opponent_resp.time_limit_per_player = game->time_limit_per_player / 1000;
125+
opponent_resp.white_time_remaining = game->white_time_remaining / 1000;
126+
opponent_resp.black_time_remaining = game->black_time_remaining / 1000;
127127

128128
// 게임 시작 시간 설정 (상대방)
129129
opponent_resp.game_start_time = malloc(sizeof(Google__Protobuf__Timestamp));

server/handlers/move.c

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -224,43 +224,25 @@ int handle_move_message(int fd, ClientMessage *req) {
224224

225225
LOG_INFO("Move applied successfully for fd=%d: %s -> %s", fd, move_req->from, move_req->to);
226226

227-
// 타이머 업데이트
228-
time_t current_time = time(NULL);
229-
time_t elapsed_time = current_time - game->last_move_time;
230-
231-
// 이전 플레이어의 남은 시간에서 경과 시간 차감
232-
if (player_team == WHITE) {
233-
game->white_time_remaining -= (int32_t)elapsed_time;
234-
if (game->white_time_remaining < 0) {
235-
game->white_time_remaining = 0;
236-
}
237-
} else {
238-
game->black_time_remaining -= (int32_t)elapsed_time;
239-
if (game->black_time_remaining < 0) {
240-
game->black_time_remaining = 0;
241-
}
242-
}
227+
// 타이머 업데이트 - 밀리초 단위로 정밀하게 관리
228+
// 타이머 스레드가 시간 차감을 담당하므로 여기서는 시간 기록만 업데이트
229+
extern MatchManager g_match_manager;
230+
extern int64_t get_current_time_ms(void); // 함수 선언
231+
232+
pthread_mutex_lock(&g_match_manager.mutex);
233+
234+
// 다음 턴을 위해 마지막 이동 시간만 업데이트 (밀리초 단위)
235+
// 시간 차감은 타이머 스레드가 지속적으로 처리하고 있음
236+
game->last_move_time_ms = get_current_time_ms();
243237

244-
// 다음 턴을 위해 마지막 이동 시간 업데이트
245-
game->last_move_time = current_time;
238+
// last_timer_check_ms는 타이머 스레드에서만 업데이트하도록 함
239+
// 이렇게 하면 타이머 스레드가 정확한 경과 시간을 계산할 수 있음
240+
241+
pthread_mutex_unlock(&g_match_manager.mutex);
246242

247243
LOG_DEBUG("Timer updated for game %s: white=%d, black=%d",
248244
game->game_id, game->white_time_remaining, game->black_time_remaining);
249245

250-
// 시간 초과 체크
251-
bool timeout_occurred = false;
252-
Team timeout_winner = TEAM__TEAM_UNSPECIFIED;
253-
254-
if (game->white_time_remaining <= 0) {
255-
LOG_INFO("White player timeout in game %s", game->game_id);
256-
timeout_occurred = true;
257-
timeout_winner = TEAM__TEAM_BLACK;
258-
} else if (game->black_time_remaining <= 0) {
259-
LOG_INFO("Black player timeout in game %s", game->game_id);
260-
timeout_occurred = true;
261-
timeout_winner = TEAM__TEAM_WHITE;
262-
}
263-
264246
// TODO: FEN 문자열 생성 (현재는 간단한 메시지로 대체)
265247
char fen_placeholder[256];
266248
snprintf(fen_placeholder, sizeof(fen_placeholder), "move_%d_to_%d",
@@ -286,13 +268,8 @@ int handle_move_message(int fd, ClientMessage *req) {
286268
(current_side == TEAM_WHITE) ? "WHITE" : "BLACK", game->game_id);
287269
}
288270

289-
// 게임 종료 조건 확인
290-
if (timeout_occurred) {
291-
LOG_INFO("Game %s ended by timeout", game->game_id);
292-
game_ends = true;
293-
winner_team = timeout_winner;
294-
end_type = GAME_END_TYPE__GAME_END_TIMEOUT;
295-
} else if (is_checkmate(&game->game_state)) {
271+
// 게임 종료 조건 확인 (시간 초과는 타이머 스레드에서 처리)
272+
if (is_checkmate(&game->game_state)) {
296273
LOG_INFO("Game %s ended by checkmate", game->game_id);
297274
game_ends = true;
298275
winner_team = (current_side == TEAM_WHITE) ? TEAM__TEAM_BLACK : TEAM__TEAM_WHITE;
@@ -310,21 +287,23 @@ int handle_move_message(int fd, ClientMessage *req) {
310287
}
311288

312289
// 상대방에게 이동 브로드캐스트 (게임 상태 정보 포함)
290+
// 밀리초를 초 단위로 변환해서 전송 (클라이언트 호환성 유지)
313291
if (broadcast_move_with_state(opponent_fd, game->game_id, player_id,
314292
move_req->from, move_req->to,
315293
game_ends, winner_team, end_type,
316294
is_check_situation, checked_team,
317-
game->white_time_remaining, game->black_time_remaining) < 0) {
295+
game->white_time_remaining / 1000, game->black_time_remaining / 1000) < 0) {
318296
LOG_ERROR("Failed to broadcast move to opponent fd=%d", opponent_fd);
319297
// 이미 이동은 적용되었으므로, 브로드캐스트 실패만 로그하고 계속 진행
320298
}
321299

322300
// 요청자에게도 이동 브로드캐스트 (게임 상태 정보 포함)
301+
// 밀리초를 초 단위로 변환해서 전송 (클라이언트 호환성 유지)
323302
if (broadcast_move_with_state(fd, game->game_id, player_id,
324303
move_req->from, move_req->to,
325304
game_ends, winner_team, end_type,
326305
is_check_situation, checked_team,
327-
game->white_time_remaining, game->black_time_remaining) < 0) {
306+
game->white_time_remaining / 1000, game->black_time_remaining / 1000) < 0) {
328307
LOG_ERROR("Failed to broadcast move to requester fd=%d", fd);
329308
// 이미 이동은 적용되었으므로, 브로드캐스트 실패만 로그하고 계속 진행
330309
}

server/match_manager.c

Lines changed: 172 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,28 @@
44
#include <stdio.h>
55
#include <stdlib.h>
66
#include <string.h>
7+
#include <sys/time.h> // gettimeofday를 위해 추가
78
#include <unistd.h>
89

910
#include "config.h"
1011
#include "logger.h"
1112
#include "network.h"
1213
#include "utils.h" // 체스판 초기화를 위해 추가
1314

15+
// 타이머 체크 스레드 관련 변수
16+
bool timer_thread_running = false;
17+
pthread_t timer_thread_id = 0;
18+
1419
// 매칭 매니저 전역 인스턴스
1520
MatchManager g_match_manager;
1621

22+
// 밀리초 단위 현재 시간 가져오기
23+
int64_t get_current_time_ms(void) {
24+
struct timeval tv;
25+
gettimeofday(&tv, NULL);
26+
return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
27+
}
28+
1729
// 매칭 매니저 초기화
1830
int init_match_manager(void) {
1931
memset(&g_match_manager, 0, sizeof(MatchManager));
@@ -26,12 +38,22 @@ int init_match_manager(void) {
2638
g_match_manager.waiting_count = 0;
2739
g_match_manager.active_game_count = 0;
2840

41+
// 타이머 체크 스레드 시작
42+
if (start_timer_thread() != 0) {
43+
LOG_ERROR("Failed to start timer thread");
44+
pthread_mutex_destroy(&g_match_manager.mutex);
45+
return -1;
46+
}
47+
2948
LOG_INFO("Match manager initialized");
3049
return 0;
3150
}
3251

3352
// 매칭 매니저 정리
3453
void cleanup_match_manager(void) {
54+
// 타이머 스레드 먼저 종료
55+
stop_timer_thread();
56+
3557
pthread_mutex_lock(&g_match_manager.mutex);
3658

3759
// 대기 중인 플레이어들 정리
@@ -48,6 +70,150 @@ void cleanup_match_manager(void) {
4870
LOG_INFO("Match manager cleaned up");
4971
}
5072

73+
// 타이머 체크 스레드 시작
74+
int start_timer_thread(void) {
75+
timer_thread_running = true;
76+
77+
if (pthread_create(&timer_thread_id, NULL, timer_check_thread, NULL) != 0) {
78+
LOG_ERROR("Failed to create timer thread");
79+
timer_thread_running = false;
80+
return -1;
81+
}
82+
83+
LOG_INFO("Timer check thread started");
84+
return 0;
85+
}
86+
87+
// 타이머 체크 스레드 종료
88+
void stop_timer_thread(void) {
89+
if (timer_thread_running) {
90+
timer_thread_running = false;
91+
92+
if (timer_thread_id != 0) {
93+
pthread_join(timer_thread_id, NULL);
94+
timer_thread_id = 0;
95+
LOG_INFO("Timer check thread stopped");
96+
}
97+
}
98+
}
99+
100+
// 타이머 체크 스레드 메인 함수 (100ms 간격으로 정밀하게 체크)
101+
void *timer_check_thread(void *arg) {
102+
LOG_INFO("Timer check thread started");
103+
104+
while (timer_thread_running) {
105+
check_game_timeouts();
106+
107+
// 100ms마다 체크 (더 정밀한 타이머)
108+
usleep(100000); // 100ms = 100,000 microseconds
109+
}
110+
111+
LOG_INFO("Timer check thread terminated");
112+
return NULL;
113+
}
114+
115+
// 게임 타이머 체크 및 시간 초과 처리 (밀리초 단위로 정밀도 향상)
116+
void check_game_timeouts(void) {
117+
int64_t current_time_ms = get_current_time_ms();
118+
119+
pthread_mutex_lock(&g_match_manager.mutex);
120+
121+
for (int i = 0; i < MAX_ACTIVE_GAMES; i++) {
122+
ActiveGame *game = &g_match_manager.active_games[i];
123+
124+
if (!game->is_active) {
125+
continue;
126+
}
127+
128+
// 마지막 타이머 체크 이후 경과 시간만 차감 (중복 차감 방지)
129+
int64_t time_since_last_check_ms = current_time_ms - game->last_timer_check_ms;
130+
131+
// 100ms 미만이면 건너뜀 (너무 빈번한 업데이트 방지)
132+
if (time_since_last_check_ms < 100) {
133+
continue;
134+
}
135+
136+
team_t current_player = game->game_state.side_to_move;
137+
138+
bool timeout_occurred = false;
139+
Team timeout_winner = TEAM__TEAM_UNSPECIFIED;
140+
const char *timeout_player_id = NULL;
141+
142+
// 현재 턴인 플레이어의 시간만 차감 (밀리초 단위)
143+
if (current_player == TEAM_WHITE) {
144+
// 백 팀 턴인 경우
145+
game->white_time_remaining -= (int32_t)time_since_last_check_ms;
146+
if (game->white_time_remaining <= 0) {
147+
LOG_INFO("White player timeout in game %s (elapsed_ms: %ld, remaining was: %d ms)",
148+
game->game_id, time_since_last_check_ms, game->white_time_remaining + (int32_t)time_since_last_check_ms);
149+
timeout_occurred = true;
150+
timeout_winner = TEAM__TEAM_BLACK;
151+
timeout_player_id = game->white_player_id;
152+
game->white_time_remaining = 0;
153+
}
154+
} else {
155+
// 흑 팀 턴인 경우
156+
game->black_time_remaining -= (int32_t)time_since_last_check_ms;
157+
if (game->black_time_remaining <= 0) {
158+
LOG_INFO("Black player timeout in game %s (elapsed_ms: %ld, remaining was: %d ms)",
159+
game->game_id, time_since_last_check_ms, game->black_time_remaining + (int32_t)time_since_last_check_ms);
160+
timeout_occurred = true;
161+
timeout_winner = TEAM__TEAM_WHITE;
162+
timeout_player_id = game->black_player_id;
163+
game->black_time_remaining = 0;
164+
}
165+
}
166+
167+
// 마지막 체크 시간 업데이트
168+
game->last_timer_check_ms = current_time_ms;
169+
170+
if (timeout_occurred) {
171+
LOG_INFO("Game %s ended by timeout - winner: %s",
172+
game->game_id,
173+
(timeout_winner == TEAM__TEAM_WHITE) ? "WHITE" : "BLACK");
174+
175+
// 게임 종료 브로드캐스트 전송
176+
send_timeout_game_end_broadcast(game, timeout_player_id, timeout_winner);
177+
178+
// 게임 제거
179+
game->is_active = false;
180+
g_match_manager.active_game_count--;
181+
}
182+
}
183+
184+
pthread_mutex_unlock(&g_match_manager.mutex);
185+
}
186+
187+
// 시간 초과로 인한 게임 종료 브로드캐스트 전송
188+
int send_timeout_game_end_broadcast(ActiveGame *game, const char *timeout_player_id, Team winner_team) {
189+
ServerMessage game_end_msg = SERVER_MESSAGE__INIT;
190+
GameEndBroadcast game_end_broadcast = GAME_END_BROADCAST__INIT;
191+
192+
game_end_broadcast.game_id = game->game_id;
193+
game_end_broadcast.player_id = (char *)timeout_player_id;
194+
game_end_broadcast.winner_team = winner_team;
195+
game_end_broadcast.end_type = GAME_END_TYPE__GAME_END_TIMEOUT;
196+
197+
game_end_msg.msg_case = SERVER_MESSAGE__MSG_GAME_END;
198+
game_end_msg.game_end = &game_end_broadcast;
199+
200+
// 양쪽 플레이어 모두에게 게임 종료 브로드캐스트 전송
201+
int result = 0;
202+
if (send_server_message(game->white_player_fd, &game_end_msg) < 0) {
203+
LOG_ERROR("Failed to send timeout game end broadcast to white player fd=%d", game->white_player_fd);
204+
result = -1;
205+
}
206+
if (send_server_message(game->black_player_fd, &game_end_msg) < 0) {
207+
LOG_ERROR("Failed to send timeout game end broadcast to black player fd=%d", game->black_player_fd);
208+
result = -1;
209+
}
210+
211+
LOG_INFO("Sent timeout game end broadcast for game %s (timeout_player=%s, winner_team=%d)",
212+
game->game_id, timeout_player_id, winner_team);
213+
214+
return result;
215+
}
216+
51217
// 게임 ID 생성
52218
char *generate_game_id(void) {
53219
static char game_id[GAME_ID_LENGTH + 1];
@@ -95,11 +261,12 @@ MatchResult add_player_to_matching(int fd, const char *player_id) {
95261
// 체스판 초기화 (표준 시작 위치)
96262
init_startpos(&game->game_state);
97263

98-
// 타이머 설정
99-
game->time_limit_per_player = DEFAULT_GAME_TIME_LIMIT;
100-
game->white_time_remaining = DEFAULT_GAME_TIME_LIMIT;
101-
game->black_time_remaining = DEFAULT_GAME_TIME_LIMIT;
102-
game->last_move_time = game->game_start_time;
264+
// 타이머 설정 (밀리초 단위)
265+
game->time_limit_per_player = DEFAULT_GAME_TIME_LIMIT * 1000; // 초를 밀리초로 변환
266+
game->white_time_remaining = DEFAULT_GAME_TIME_LIMIT * 1000; // 초를 밀리초로 변환
267+
game->black_time_remaining = DEFAULT_GAME_TIME_LIMIT * 1000; // 초를 밀리초로 변환
268+
game->last_move_time_ms = get_current_time_ms();
269+
game->last_timer_check_ms = get_current_time_ms();
103270

104271
if (current_is_white) {
105272
game->white_player_fd = fd;

0 commit comments

Comments
 (0)