OSDN Git Service

バージョン番号変更。
[gvonavish/GVONavish.git] / GVONavish / GVONavish / GVONavish.cpp
1 #include "stdafx.h"
2 #include <vector>
3 #include <list>
4
5 // PNG\82Æ\82©\82Ì\93Ç\82Ý\8d\9e\82Ý\97p
6 #include <gdiplus.h>
7 #pragma comment(lib, "gdiplus.lib")
8
9 // PATH API\97p
10 #include <Shlwapi.h>
11 #pragma comment(lib, "shlwapi.lib")
12
13 #include <CommCtrl.h>
14 #pragma comment(lib, "comctl32.lib")
15 #include <CommDlg.h>
16 #pragma comment(lib, "Comdlg32.lib")
17
18
19 #include "GVONavish.h"
20 #include "GVOConfig.h"
21 #include "GVOGameProcess.h"
22 #include "GVOWorldMap.h"
23 #include "GVOShip.h"
24 #include "GVOSpeedMeter.h"
25
26
27
28
29 // \83f\83o\83b\83O\8e\9e\82Ì\95`\89æ\83p\83t\83H\81[\83}\83\93\83X\91ª\92è\97p\81B
30 //#define GVO_PERF_CHECK
31
32
33
34 // \83O\83\8d\81[\83o\83\8b\95Ï\90\94:
35 HINSTANCE g_hinst;                                                                              // \8c»\8dÝ\82Ì\83C\83\93\83^\81[\83t\83F\83C\83X
36 HWND g_hwndMain;
37 HDC g_hdcMain;
38
39
40 // \8aÖ\90\94\83v\83\8d\83g\83^\83C\83v\90é\8c¾
41 static ATOM MyRegisterClass( HINSTANCE hInstance );
42 static BOOL InitInstance( HINSTANCE, int );
43 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );
44 static LRESULT s_mainLoop();
45
46
47 // \83\81\83b\83Z\81[\83W\83n\83\93\83h\83\89
48 static bool s_onCreate( HWND, LPCREATESTRUCT );
49 static void s_onMove( HWND, WORD, WORD );
50 static void s_onSize( HWND, UINT, WORD, WORD );
51 static void s_onMouseWheel( HWND, int16_t, UINT, int16_t, int16_t );
52 static void s_onMouseMove( HWND, UINT, int16_t, int16_t );
53 static void s_onMouseLeftButtonDown( HWND, UINT, int16_t, int16_t );
54 static void s_onMouseLeftButtonUp( HWND, UINT, int16_t, int16_t );
55 static void s_onMouseLeftButtonDoubleClick( HWND, UINT, int16_t, int16_t );
56 static void s_onMouseRightButtonUp( HWND, UINT, int16_t, int16_t );
57 static void s_onPaint( HWND );
58
59
60 // \83A\83v\83\8a\8f\88\97\9d
61 static std::wstring s_makeVersionString();
62 static std::wstring s_getMapFileName();
63 static void s_updateFrame(HWND hwnd, HDC hdc);
64 static void s_updateWindowTitle( HWND );
65 static void s_popupMenu( HWND, int16_t, int16_t );
66 static void s_popupCoord( HWND, int16_t, int16_t );
67
68
69 // \83\8d\81[\83J\83\8b\95Ï\90\94
70 static LPCWSTR const k_appName = L"GVANavi\81i\82Á\82Û\82¢\89½\82©\81j";      // \83A\83v\83\8a\83P\81[\83V\83\87\83\93\96¼
71 static LPCWSTR const k_version = L"ver 1.0\83¿4"; // \83o\81[\83W\83\87\83\93\94Ô\8d\86
72 static LPCWSTR const k_copyright = L"copyright(c) mandheling";  // \92\98\8dì\8c \95\\8e¦\81i\82¢\82¿\82¨\81[\81j
73
74 static LPCWSTR const k_windowClassName = L"GVANavish";          // \83\81\83C\83\93 \83E\83B\83\93\83h\83\83N\83\89\83X\96¼
75 static const LPCWSTR k_configFileName = L"GVONavish.ini";       // \90Ý\92è\83t\83@\83C\83\8b\96¼
76
77 static const std::wstring k_aboutText = s_makeVersionString();  // \83o\81[\83W\83\87\83\93\8fî\95ñ\83e\83L\83X\83g
78
79 static GVOImage s_backbuffer;   //!<@brief \95`\89æ\97p24bit\83C\83\81\81[\83W\83o\83b\83t\83@
80 static HANDLE s_pollingTimerEvent = ::CreateEvent( NULL, TRUE, TRUE, NULL );
81 static UINT s_pollingTimerID = 0;
82
83 static Gdiplus::GdiplusStartupInput s_gdisi;
84 static ULONG_PTR s_gdiToken;
85
86 static GVOConfig s_config( k_configFileName );
87 static GVOGameProcess s_gvoGameProcess;
88 static GVOWorldMap s_worldMap;
89 static GVOShip s_ship;
90 static GVOSpeedMeter s_speedMeter;
91
92 static UINT s_pollingInterval = 1000;   // \8fó\91Ô\8aÄ\8e\8b\8aÔ\8au\81i1\95b\81j
93 static bool s_isUpdated = false;                // \8fó\91Ô\8dX\90V\83t\83\89\83O
94 static bool s_isDragging = false;               // \83h\83\89\83b\83O\8fó\91Ô\83t\83\89\83O
95 static SIZE s_clientSize;                               // \83N\83\89\83C\83A\83\93\83g\97Ì\88æ\82Ì\91å\82«\82³
96 static POINT s_dragOrg;                                 // \83h\83\89\83b\83O\8c´\93_\81i\88Ú\93®\97Ê\8eZ\8fo\97p\81j
97
98
99 #ifndef NDEBUG
100 // \83f\83o\83b\83O\97p\8e©\93®\8dq\8ds\95Ï\90\94
101 static double s_xDebugAutoCruise;
102 static double s_yDebugAutoCruise;
103 static double s_debugAutoCruiseAngle = 0;
104 #endif
105
106
107
108
109 int APIENTRY _tWinMain( _In_ HINSTANCE hInstance,
110         _In_opt_ HINSTANCE hPrevInstance,
111         _In_ LPTSTR    lpCmdLine,
112         _In_ int       nCmdShow )
113 {
114         UNREFERENCED_PARAMETER( hPrevInstance );
115         UNREFERENCED_PARAMETER( lpCmdLine );
116         TIMECAPS tc;
117
118         ::CoInitialize( NULL );
119         ::timeGetDevCaps( &tc, sizeof(tc) );
120         ::timeBeginPeriod( tc.wPeriodMin );
121         INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_WIN95_CLASSES };
122         ::InitCommonControlsEx( &icc );
123         GdiplusStartup( &s_gdiToken, &s_gdisi, NULL );
124         s_config.load();
125
126         MyRegisterClass( hInstance );
127
128         // \83A\83v\83\8a\83P\81[\83V\83\87\83\93\82Ì\8f\89\8aú\89»\82ð\8eÀ\8ds\82µ\82Ü\82·:
129         if ( !InitInstance( hInstance, nCmdShow ) ) {
130                 return 0;
131         }
132
133         s_pollingTimerID = ::timeSetEvent( s_config.m_pollingInterval, tc.wPeriodMin, LPTIMECALLBACK( s_pollingTimerEvent ), 0, TIME_PERIODIC | TIME_CALLBACK_EVENT_SET );
134
135         // \83\81\83C\83\93 \83\81\83b\83Z\81[\83\83\8b\81[\83v:
136         const LRESULT retVal = s_mainLoop();
137
138         ::timeKillEvent( s_pollingTimerID );
139
140         s_config.save();
141         Gdiplus::GdiplusShutdown( s_gdiToken );
142         ::timeEndPeriod( tc.wPeriodMin );
143         ::CoUninitialize();
144         return retVal;
145 }
146
147 static ATOM MyRegisterClass( HINSTANCE hInstance )
148 {
149         WNDCLASSEX wcex = { sizeof(wcex) };
150
151         wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC;
152         wcex.lpfnWndProc = WndProc;
153         wcex.hInstance = hInstance;
154         wcex.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDR_MAINFRAME ) );
155         wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
156         wcex.hbrBackground = (HBRUSH)::GetStockObject( BLACK_BRUSH );
157         //wcex.lpszMenuName     = MAKEINTRESOURCE(IDC_GVONAVISH);
158         wcex.lpszClassName = k_windowClassName;
159         wcex.hIconSm = LoadIcon( wcex.hInstance, MAKEINTRESOURCE( IDI_SMALL ) );
160
161         return RegisterClassEx( &wcex );
162 }
163
164 static BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )
165 {
166         if ( !s_worldMap.loadFromFile( s_config ) ) {
167                 // \8e¸\94s\82·\82ê\82Î\95Û\91\82³\82ê\82È\82¢\82Ì\82Å\91å\8fä\95v\81B
168                 s_config.m_mapFileName = s_getMapFileName();
169                 if ( !s_worldMap.loadFromFile( s_config ) ) {
170                         ::MessageBox( NULL,
171                                 L"\83}\83b\83v\89æ\91\9c\82ð\8aJ\82¯\82Ü\82¹\82ñ\82Å\82µ\82½\81B",
172                                 k_appName,
173                                 MB_ICONERROR | MB_SETFOREGROUND | MB_OK );
174                         return FALSE;
175                 }
176         }
177
178         HWND hwnd;
179
180         g_hinst = hInstance; // \83O\83\8d\81[\83o\83\8b\95Ï\90\94\82É\83C\83\93\83X\83^\83\93\83X\8f\88\97\9d\82ð\8ai\94[\82µ\82Ü\82·\81B
181
182         hwnd = CreateWindow( k_windowClassName, k_appName, WS_OVERLAPPEDWINDOW,
183                 s_config.m_windowPos.x, s_config.m_windowPos.y,
184                 s_config.m_windowSize.cx, s_config.m_windowSize.cy,
185                 NULL, NULL, hInstance, NULL );
186
187         if ( !hwnd ) {
188                 return FALSE;
189         }
190
191         s_pollingInterval = s_config.m_pollingInterval;
192         s_gvoGameProcess.setConfig( s_config );
193         s_worldMap.setConfig( s_config );
194         s_ship.setInitialSurveyCoord( s_config.m_initialSurveyCoord );
195 #ifndef NDEBUG
196         s_xDebugAutoCruise = s_config.m_initialSurveyCoord.x;
197         s_yDebugAutoCruise = s_config.m_initialSurveyCoord.y;
198 #endif
199
200         if ( s_gvoGameProcess.updateState() ) {
201                 s_ship.updateWithSurveyCoord( s_gvoGameProcess.surveyCoord(), s_gvoGameProcess.timeStamp() );
202                 s_worldMap.setShipPosition( s_gvoGameProcess.surveyCoord(), s_config.m_traceShipPositionEnabled );
203         }
204         s_updateWindowTitle( hwnd );
205
206         ShowWindow( hwnd, nCmdShow );
207         UpdateWindow( hwnd );
208         g_hwndMain = hwnd;
209         g_hdcMain = ::GetDC( g_hwndMain );
210
211         return TRUE;
212 }
213
214 static LRESULT s_mainLoop()
215 {
216         MSG msg;
217         HACCEL hAccelTable;
218
219         hAccelTable = LoadAccelerators( g_hinst, MAKEINTRESOURCE( IDC_GVONAVISH ) );
220
221         for ( ;; ) {
222                 if ( ::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
223                         if ( msg.message == WM_QUIT ) {
224                                 break;
225                         }
226                         if ( !TranslateAccelerator( msg.hwnd, hAccelTable, &msg ) ) {
227                                 TranslateMessage( &msg );
228                                 DispatchMessage( &msg );
229                         }
230                         continue;
231                 }
232                 std::vector<HANDLE> handles;
233
234                 // \8aÄ\8e\8b\82·\82é\83n\83\93\83h\83\8b\82ð\92Ç\89Á\82·\82é\81B
235                 if ( s_gvoGameProcess.processHandle() ) {
236                         handles.push_back( s_gvoGameProcess.processHandle() );
237                 }
238                 handles.push_back( s_pollingTimerEvent );
239
240                 if ( handles.empty() ) {
241                         ::WaitMessage();
242                         continue;
243                 }
244
245                 DWORD const waitResult = ::MsgWaitForMultipleObjects( handles.size(), &handles[0], FALSE, INFINITE, QS_ALLINPUT );
246                 if ( handles.size() <= waitResult ) {
247                         continue;
248                 }
249
250                 // \8aÄ\8e\8b\82·\82é\83n\83\93\83h\83\8b\82É\91Î\89\9e\82·\82é\81B
251                 HANDLE const activeHandle = handles[waitResult];
252
253                 if ( activeHandle == s_gvoGameProcess.processHandle() ) {
254                         // \83Q\81[\83\80\83v\83\8d\83Z\83X\82ª\8fI\97¹\82µ\82½\81B
255                         s_gvoGameProcess.clear();
256                         continue;
257                 }
258                 if ( activeHandle == s_pollingTimerEvent ) {
259                         ::ResetEvent( s_pollingTimerEvent );
260                         s_updateFrame( g_hwndMain, g_hdcMain );
261                         continue;
262                 }
263         }
264         return (int)msg.wParam;
265 }
266
267
268 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wp, LPARAM lp )
269 {
270         int wmId, wmEvent;
271
272         switch ( message ) {
273         case WM_ERASEBKGND:
274                 return TRUE;
275         case WM_PAINT:
276                 s_onPaint( hwnd );
277                 break;
278
279         case WM_MOVE:
280                 s_onMove( hwnd, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
281                 break;
282         case WM_SIZE:
283                 s_onSize( hwnd, wp, LOWORD( lp ), HIWORD( lp ) );
284                 break;
285
286         case WM_COMMAND:
287                 wmId = LOWORD( wp );
288                 wmEvent = HIWORD( wp );
289                 // \91I\91ð\82³\82ê\82½\83\81\83j\83\85\81[\82Ì\89ð\90Í:
290                 switch ( wmId ) {
291                 case IDM_ABOUT:
292                         ::MessageBox( hwnd,
293                                 k_aboutText.c_str(),
294                                 k_appName,
295                                 MB_OK | MB_ICONINFORMATION );
296                         break;
297                 case IDM_EXIT:
298                         DestroyWindow( hwnd );
299                         break;
300                 case IDM_TOGGLE_TRACE_SHIP:
301                         s_config.m_traceShipPositionEnabled = !s_config.m_traceShipPositionEnabled;
302                         break;
303                 case IDM_ERASE_SHIP_ROUTE:
304                         s_worldMap.clearShipRoute();
305                         break;
306                 default:
307                         return DefWindowProc( hwnd, message, wp, lp );
308                 }
309                 break;
310
311         case WM_MOUSEWHEEL:
312                 s_onMouseWheel( hwnd, HIWORD( wp ), LOWORD( wp ), int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
313                 break;
314         case WM_MOUSEMOVE:
315                 s_onMouseMove( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
316                 break;
317         case WM_LBUTTONDOWN:
318                 s_onMouseLeftButtonDown( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
319                 break;
320         case WM_LBUTTONUP:
321                 s_onMouseLeftButtonUp( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
322                 break;
323         case WM_RBUTTONUP:
324                 s_onMouseRightButtonUp( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
325                 break;
326         case WM_LBUTTONDBLCLK:
327                 s_onMouseLeftButtonDoubleClick( hwnd, wp, int16_t( LOWORD( lp ) ), int16_t( HIWORD( lp ) ) );
328                 break;
329
330         case WM_CREATE:
331                 if ( !s_onCreate( hwnd, reinterpret_cast<LPCREATESTRUCT>(lp) ) ) {
332                         return -1;
333                 }
334                 break;
335         case WM_DESTROY:
336                 PostQuitMessage( 0 );
337                 break;
338         default:
339                 return DefWindowProc( hwnd, message, wp, lp );
340         }
341         return 0;
342 }
343
344
345 static bool s_onCreate( HWND hwnd, LPCREATESTRUCT /*cs*/ )
346 {
347         return true;
348 }
349
350
351 static void s_onMove( HWND hwnd, WORD /*cx*/, WORD /*cy*/ )
352 {
353         const DWORD style = ::GetWindowLong( hwnd, GWL_STYLE );
354         if ( style & WS_MAXIMIZE ) {
355                 return;
356         }
357         RECT rc = { 0 };
358         ::GetWindowRect( hwnd, &rc );
359         s_config.m_windowPos.x = rc.left;
360         s_config.m_windowPos.y = rc.top;
361 }
362
363
364 static void s_onSize( HWND hwnd, UINT state, WORD cx, WORD cy )
365 {
366         RECT rc = { 0 };
367
368         switch ( state ) {
369         case SIZE_RESTORED:
370                 ::GetWindowRect( hwnd, &rc );
371                 s_config.m_windowSize.cx = rc.right - rc.left;
372                 s_config.m_windowSize.cy = rc.bottom - rc.top;
373                 break;
374         case SIZE_MAXIMIZED:
375                 break;
376         default:
377                 return;
378         }
379
380         if ( s_clientSize.cx != cx || s_clientSize.cy != cy ) {
381                 s_clientSize.cx = cx;
382                 s_clientSize.cy = cy;
383                 if ( !s_backbuffer.isCompatible( s_clientSize ) ) {
384                         s_backbuffer.createDIBImage( s_clientSize );
385                 }
386                 s_worldMap.setViewSize( s_clientSize );
387         }
388 }
389
390
391 static void s_onMouseWheel( HWND hwnd, int16_t delta, UINT vkey, int16_t x, int16_t y )
392 {
393         bool isChanged = false;
394
395         if ( 0 < delta ) {
396                 isChanged = s_worldMap.zoomIn();
397         }
398         else {
399                 isChanged = s_worldMap.zoomOut();
400         }
401
402         if ( isChanged ) {
403                 s_updateWindowTitle( hwnd );
404                 ::InvalidateRect( hwnd, NULL, FALSE );
405         }
406 }
407
408
409 static void s_onMouseMove( HWND hwnd, UINT vkey, int16_t x, int16_t y )
410 {
411         if ( s_isDragging ) {
412                 const int dx = x - s_dragOrg.x;
413                 const int dy = y - s_dragOrg.y;
414                 const int threshold = 1;        // \8a´\93x\82ª\97Ç\82·\82¬\82é\82Æ\92Ç\8f]\82ª\8aÈ\92P\82É\90Ø\82ê\82Ä\82µ\82Ü\82¤\82Ì\82Å\93K\93\96\82É\91Î\8dô
415                 if ( s_config.m_traceShipPositionEnabled ) {
416                         if ( ::abs( dx ) <= threshold && ::abs( dy ) < threshold ) {
417                                 return;
418                         }
419                 }
420                 const POINT offset = { -dx, -dy };
421
422                 s_worldMap.offsetFocusInViewCoord( offset );
423                 ::InvalidateRect( hwnd, NULL, FALSE );
424
425                 s_dragOrg.x = x;
426                 s_dragOrg.y = y;
427                 s_config.m_traceShipPositionEnabled = false;
428         }
429         else {
430
431         }
432 }
433
434
435 static void s_onMouseLeftButtonDown( HWND hwnd, UINT vkey, int16_t x, int16_t y )
436 {
437         if ( s_isDragging ) {
438
439         }
440         else {
441                 ::SetCapture( hwnd );
442                 s_isDragging = true;
443                 s_dragOrg.x = x;
444                 s_dragOrg.y = y;
445         }
446 }
447
448
449 static void s_onMouseLeftButtonUp( HWND hwnd, UINT vkey, int16_t x, int16_t y )
450 {
451         if ( s_isDragging ) {
452                 ::ReleaseCapture();
453                 s_isDragging = false;
454                 s_dragOrg.x = 0;
455                 s_dragOrg.y = 0;
456         }
457         else {
458
459         }
460 }
461
462
463 static void s_onMouseLeftButtonDoubleClick( HWND hwnd, UINT vkey, int16_t x, int16_t y )
464 {
465         if ( s_isDragging ) {
466
467         }
468         else {
469                 s_popupCoord( hwnd, x, y );
470         }
471 }
472
473
474 static void s_onMouseRightButtonUp( HWND hwnd, UINT vkey, int16_t x, int16_t y )
475 {
476         if ( s_isDragging ) {
477
478         }
479         else {
480                 s_popupMenu( hwnd, x, y );
481         }
482 }
483
484
485 // \83_\83u\83\8b\83o\83b\83t\83@\83\8a\83\93\83O\82Å\82¿\82ç\82Â\82«\96h\8e~\81B
486 static void s_onPaint( HWND hwnd )
487 {
488 #ifdef GVO_PERF_CHECK
489         int64_t perfBegin = 0, perfEnd = 0;
490         ::QueryPerformanceCounter((LARGE_INTEGER*)&perfBegin);
491 #endif
492         if ( !s_backbuffer.bitmapHandle() ) {
493                 s_backbuffer.createDIBImage( s_clientSize );
494         }
495
496         PAINTSTRUCT ps;
497         HDC hdc = BeginPaint( hwnd, &ps );
498         HDC hdcBackbuffer = ::CreateCompatibleDC( hdc );
499         ::SaveDC( hdcBackbuffer );
500         ::SelectObject( hdcBackbuffer, s_backbuffer.bitmapHandle() );
501         RECT rc = { 0, 0, s_clientSize.cx, s_clientSize.cy };
502         ::FillRect( hdcBackbuffer, &rc, (HBRUSH)::GetStockObject( BLACK_BRUSH ) );
503
504         // \95`\89æ\82ðhdcBackbuffer\82É\91Î\82µ\82Ä\8ds\82¤\81B
505         s_worldMap.drawMap( hdcBackbuffer, s_ship );
506
507         // \91¬\93x\8cv\82ð\95`\89æ
508         {
509                 const double velocity = s_speedMeter.velocityByKnot();
510                 wchar_t buf[4096] = { 0 };
511                 swprintf( buf, _countof( buf ), L"%.2f kt", velocity );
512
513                 RECT rc = { 0 };
514                 ::DrawText( hdcBackbuffer, buf, -1, &rc, DT_SINGLELINE | DT_RIGHT | DT_TOP | DT_CALCRECT );
515                 const int width = rc.right - rc.left;
516                 rc.left = s_clientSize.cx - width;
517                 rc.right = rc.left + width;
518                 ::DrawText( hdcBackbuffer, buf, -1, &rc, DT_SINGLELINE | DT_RIGHT | DT_TOP );
519         }
520
521 #ifndef NDEBUG
522         // \91ª\97Ê\8dÀ\95W\82ð\95`\89æ
523         {
524                 const GVOImage& surveyCoordImage = s_gvoGameProcess.surveyCoordImage();
525                 HDC hdcSurvey = ::CreateCompatibleDC( hdcBackbuffer );
526                 ::SaveDC( hdcSurvey );
527                 ::SelectObject( hdcSurvey, surveyCoordImage.bitmapHandle() );
528                 ::BitBlt( hdcBackbuffer, 0, 0, surveyCoordImage.size().cx, surveyCoordImage.size().cy,
529                         hdcSurvey, 0, 0, SRCCOPY );
530                 ::RestoreDC( hdcSurvey, -1 );
531                 ::DeleteDC( hdcSurvey );
532         }
533 #endif
534
535 #ifndef NDEBUG
536         if ( s_config.m_debugAutoCruiseEnabled ) {
537                 const double rad = ((s_debugAutoCruiseAngle)* M_PI) / 180;
538                 const double vx = ::cos( rad );
539                 const double vy = ::sin( rad );
540                 const LONG length = max( s_clientSize.cx, s_clientSize.cy );
541
542                 COLORREF rgb = (::fabs(s_ship.vector().angleTo(GVOVector(vx,vy))) <= FLT_EPSILON) ? RGB( 0, 255, 0 ) : RGB( 255, 255, 0 );
543
544                 LONG x2 = s_clientSize.cx / 2 + LONG( vx * length );
545                 LONG y2 = s_clientSize.cy / 2 + LONG( vy * length );
546                 HPEN pen = ::CreatePen( PS_SOLID, 3, rgb );
547                 HGDIOBJ old = ::SelectObject( hdcBackbuffer, pen );
548                 ::MoveToEx( hdcBackbuffer, s_clientSize.cx / 2, s_clientSize.cy / 2, NULL );
549                 ::LineTo( hdcBackbuffer, x2, y2 );
550                 ::SelectObject( hdcBackbuffer, old );
551                 ::DeleteObject( pen );
552         }
553 #endif  // #ifndef NDEBUG
554
555         ::BitBlt( hdc, 0, 0, s_clientSize.cx, s_clientSize.cy,
556                 hdcBackbuffer, 0, 0, SRCCOPY );
557
558         ::RestoreDC( hdcBackbuffer, -1 );
559         ::DeleteDC( hdcBackbuffer );
560         EndPaint( hwnd, &ps );
561
562
563 #ifdef GVO_PERF_CHECK
564         ::QueryPerformanceCounter((LARGE_INTEGER*)&perfEnd);
565         int64_t freq = 0;
566         ::QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
567         const double deltaPerSec = (double(perfEnd - perfBegin) / double(freq)) * 1000.0;
568         typedef std::list<double> PerfList;
569         static PerfList perf;
570         perf.push_back(deltaPerSec);
571
572         const double ave = std::accumulate( perf.begin(), perf.end(), 0.0 ) / perf.size();
573         if ( 100 < perf.size() ) {
574                 perf.pop_front();
575         }
576
577         std::wstring s;
578         s = std::wstring( L"perf:" ) + std::to_wstring( ave ) + L"(ms)\n";
579         ::SetWindowText( hwnd, s.c_str() );
580         ::InvalidateRect( hwnd, NULL, FALSE );
581 #endif
582 }
583
584
585 static std::wstring s_makeVersionString()
586 {
587         std::wstring s;
588         s += std::wstring(k_appName) + L" " + k_version + L"\n";
589         s += k_copyright;
590         return s;
591 }
592
593
594 // \83}\83b\83v\89æ\91\9c\82ð\91I\91ð\82³\82¹\82é
595 static std::wstring s_getMapFileName()
596 {
597         wchar_t dir[MAX_PATH] = { 0 };
598         ::GetModuleFileName( g_hinst, dir, _countof( dir ) );
599         ::PathRemoveFileSpec( dir );
600
601         wchar_t filePath[MAX_PATH] = { 0 };
602         OPENFILENAME ofn = { sizeof(ofn) };
603         ofn.lpstrTitle = L"\83}\83b\83v\89æ\91\9c\83t\83@\83C\83\8b\82ð\91I\91ð\82µ\82Ä\82­\82¾\82³\82¢\81B";
604         ofn.lpstrInitialDir = &dir[0];
605         ofn.lpstrFilter = L"\83C\83\81\81[\83W\83t\83@\83C\83\8b\0L" L"*.bmp;*.jpg;*.jpeg;*.png;*.gif;*.tif;*.tiff" L"\0"
606                 L"\91S\82Ä\82Ì\83t\83@\83C\83\8b\0" L"*.*" L"\0\0";
607         ofn.Flags = OFN_READONLY | OFN_FILEMUSTEXIST;
608         ofn.nMaxFile = _countof( filePath );
609         ofn.lpstrFile = &filePath[0];
610         if ( !::GetOpenFileName( &ofn ) ) {
611                 return L"";
612         }
613         return filePath;
614 }
615
616
617 static void s_updateFrame( HWND hwnd, HDC hdc )
618 {
619 #ifndef NDEBUG
620         if ( s_config.m_debugAutoCruiseEnabled ) {
621                 static bool isRandInitialized = false;
622                 if ( !isRandInitialized ) {
623                         srand( ::timeGetTime() );
624                         isRandInitialized = true;
625                 }
626
627                 const double rad = ((s_debugAutoCruiseAngle)* M_PI) / 180;
628                 const double vx = ::cos( rad );
629                 const double vy = ::sin( rad );
630
631                 s_xDebugAutoCruise += vx * s_config.m_debugAutoCruiseVelocity;
632                 s_yDebugAutoCruise += vy * s_config.m_debugAutoCruiseVelocity;
633
634                 static DWORD tick = ::timeGetTime();
635                 static DWORD count = 0;
636                 if ( (tick + s_config.m_debugAutoCruiseTurnInterval) < ::timeGetTime() ) {
637                         if ( 10 < (++count) ) {
638                                 count = 0;
639                                 s_debugAutoCruiseAngle += 90 + (LONG( rand() / double( RAND_MAX ) * 90 ) & ~0x1);
640                         }
641                         else {
642                                 s_debugAutoCruiseAngle += (rand() & 1) ? s_config.m_debugAutoCruiseTurnAngle : -s_config.m_debugAutoCruiseTurnAngle;
643                         }
644                         tick = ::timeGetTime();
645                 }
646                 s_debugAutoCruiseAngle = fmod( ::fabs( s_debugAutoCruiseAngle ), 360 );
647
648                 if ( s_xDebugAutoCruise < 0 ) {
649                         s_xDebugAutoCruise += k_worldWidth;
650                 }
651                 if ( s_yDebugAutoCruise < 0 ) {
652                         s_yDebugAutoCruise += k_worldHeight;
653                 }
654                 s_xDebugAutoCruise = fmod( s_xDebugAutoCruise, (double)k_worldWidth );
655                 s_yDebugAutoCruise = fmod( s_yDebugAutoCruise, (double)k_worldHeight );
656
657                 //// \92n\90}\82ð\8c×\82®\8f\88\97\9d\82Ì\8am\94F\97p\83f\83o\83b\83O\83R\81[\83h
658                 //if ( 100 <= s_xDebugAutoCruise && s_xDebugAutoCruise <= (GVOWorldMap::k_worldWidth - 100) ) {
659                 //      s_xDebugAutoCruise = 0;
660                 //}
661
662
663                 POINT p = {
664                         LONG( s_xDebugAutoCruise ),
665                         LONG( s_yDebugAutoCruise )
666                 };
667                 uint32_t timeStamp = ::timeGetTime();
668                 s_ship.updateWithSurveyCoord( p, timeStamp );
669                 s_speedMeter.updateVelocity( s_ship.velocity(), timeStamp );
670                 s_gvoGameProcess.setSurveyCoord( p );
671                 s_worldMap.setShipPosition( p, s_config.m_traceShipPositionEnabled );
672                 s_worldMap.updateShipRouteMap( hdc );
673                 s_updateWindowTitle( hwnd );
674                 ::InvalidateRect( hwnd, NULL, FALSE );
675                 return;
676         }
677 #endif  // #ifndef NDEBUG
678
679         s_isUpdated = s_gvoGameProcess.updateState();
680
681         if ( s_isUpdated ) {
682                 s_config.m_initialSurveyCoord = s_gvoGameProcess.surveyCoord();
683                 s_ship.updateWithSurveyCoord( s_gvoGameProcess.surveyCoord(), s_gvoGameProcess.timeStamp() );
684                 s_speedMeter.updateVelocity( s_ship.velocity(), s_gvoGameProcess.timeStamp() );
685                 s_worldMap.setShipPosition( s_gvoGameProcess.surveyCoord(), s_config.m_traceShipPositionEnabled );
686                 s_worldMap.updateShipRouteMap( hdc );
687                 s_updateWindowTitle( hwnd );
688                 ::InvalidateRect( hwnd, NULL, FALSE );
689         }
690 }
691
692
693 static void s_updateWindowTitle( HWND hwnd )
694 {
695         const POINT& surveyCoord = s_gvoGameProcess.surveyCoord();
696
697         std::vector<wchar_t> buf( 4096 );
698         ::swprintf( &buf[0], buf.size(), L"%d,%d - (%.1f%%) - %s %s",
699                 surveyCoord.x, surveyCoord.y,
700                 s_worldMap.viewScale() * s_worldMap.viewScaleOrder(),
701                 k_appName, k_version
702                 );
703         ::SetWindowText( hwnd, &buf[0] );
704 }
705
706
707 static void s_popupMenu( HWND hwnd, int16_t x, int16_t y )
708 {
709         HMENU hmenu = ::LoadMenu( g_hinst, MAKEINTRESOURCE( IDC_POPUPMENU ) );
710         HMENU popupMenu = ::GetSubMenu( hmenu, 0 );
711
712         ::CheckMenuItem( popupMenu, IDM_TOGGLE_TRACE_SHIP, s_config.m_traceShipPositionEnabled ? MF_CHECKED : MF_UNCHECKED );
713
714         POINT p = { x, y };
715         ::ClientToScreen( hwnd, &p );
716         ::TrackPopupMenu( popupMenu, TPM_NONOTIFY | TPM_LEFTALIGN | TPM_TOPALIGN, p.x, p.y, 0, hwnd, NULL );
717 }
718
719
720 static void s_popupCoord( HWND hwnd, int16_t x, int16_t y )
721 {
722
723 }