diff --git a/examples/tetris-win32/cpp-src/tetris.cc b/examples/tetris-win32/cpp-src/tetris.cc index 6addb53a..c2009ee7 100644 --- a/examples/tetris-win32/cpp-src/tetris.cc +++ b/examples/tetris-win32/cpp-src/tetris.cc @@ -1,222 +1,198 @@ +/** + * Tetris Win32 API Layer + * + * C++ only wraps Win32 APIs as thin drawing primitives. + * ALL game logic lives in PHP to showcase the AOT compiler. + */ + #include #include -#include +#include using namespace php; -// Game constants -#define BLOCK_SIZE 30 -#define BOARD_WIDTH 10 -#define BOARD_HEIGHT 20 - -// Colors for each piece type -static const COLORREF COLORS[7] = { - RGB(0, 255, 255), // I - Cyan - RGB(255, 255, 0), // O - Yellow - RGB(128, 0, 128), // T - Purple - RGB(0, 255, 0), // S - Green - RGB(255, 0, 0), // Z - Red - RGB(0, 0, 255), // J - Blue - RGB(255, 165, 0) // L - Orange -}; - -// Simple game state - must inherit from Box -class TetrisBox : public Box { -public: - int board[BOARD_HEIGHT][BOARD_WIDTH]; - int score; - bool gameOver; - - TetrisBox() : score(0), gameOver(false) { - memset(board, 0, sizeof(board)); - } - - void reset() { - score = 0; - gameOver = false; - memset(board, 0, sizeof(board)); - } -}; - -// Create new game instance - returns Box -var php_tetris_new() { - return {new TetrisBox()}; -} - -// Reset game -void php_tetris_reset(var box) { - auto tetris = box.toBox(); - tetris->reset(); -} - -// Get score -Int php_tetris_get_score(var box) { - auto tetris = box.toBox(); - return tetris->score; -} - -// Check if game over -Bool php_tetris_is_game_over(var box) { - auto tetris = box.toBox(); - return tetris->gameOver; -} - -// Move piece down -Bool php_tetris_move_down(var box) { - auto tetris = box.toBox(); - if (tetris->gameOver) return false; - // Simplified: just increase score for testing - tetris->score += 10; - return true; -} - -// Move piece left -Bool php_tetris_move_left(var box) { - auto tetris = box.toBox(); - if (tetris->gameOver) return false; - return true; -} - -// Move piece right -Bool php_tetris_move_right(var box) { - auto tetris = box.toBox(); - if (tetris->gameOver) return false; - return true; -} - -// Rotate piece -void php_tetris_rotate(var box) { - auto tetris = box.toBox(); - if (!tetris->gameOver) { - tetris->score += 5; - } -} - -// Hard drop -void php_tetris_hard_drop(var box) { - auto tetris = box.toBox(); - if (!tetris->gameOver) { - tetris->score += 50; +// ============================================================ +// Win32 Window & Message +// ============================================================ + +static bool g_quitRequested = false; + +LRESULT CALLBACK TetrisWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_CLOSE: + g_quitRequested = true; + PostQuitMessage(0); + return 0; + case WM_DESTROY: + g_quitRequested = true; + PostQuitMessage(0); + return 0; } + return DefWindowProc(hWnd, msg, wParam, lParam); } -// Get board state -Array php_tetris_get_board(var box) { - auto tetris = box.toBox(); - Array result; - for (int i = 0; i < BOARD_HEIGHT; i++) { - Array row; - for (int j = 0; j < BOARD_WIDTH; j++) { - row.append(tetris->board[i][j]); - } - result.append(row); - } - return result; -} +// Create window, returns hWnd +Int php_win_create_window(String title, Int width, Int height) { + SetConsoleOutputCP(65001); -// Get current piece info -Array php_tetris_get_current_piece(var box) { - Array result; - result.append(0); // shape - result.append(5); // x position - result.append(0); // y position - result.append(0); // type - return result; -} - -// Create game window -Int php_tetris_create_window(String title) { WNDCLASS wc; ZeroMemory(&wc, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpfnWndProc = DefWindowProc; + wc.lpfnWndProc = TetrisWndProc; wc.hInstance = GetModuleHandle(NULL); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = "TetrisWindow"; - RegisterClass(&wc); - + HWND hWnd = CreateWindowEx( - 0, - "TetrisWindow", - title.data(), + 0, "TetrisWindow", title.data(), WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, - CW_USEDEFAULT, - CW_USEDEFAULT, - BLOCK_SIZE * BOARD_WIDTH + 200, - BLOCK_SIZE * BOARD_HEIGHT + 40, - NULL, - NULL, - GetModuleHandle(NULL), - NULL + CW_USEDEFAULT, CW_USEDEFAULT, + (int)width, (int)height, + NULL, NULL, GetModuleHandle(NULL), NULL ); - return (Int)hWnd; } // Show window -Bool php_tetris_show_window(Int hWnd, Int cmdShow) { - return ShowWindow((HWND)hWnd, (int)cmdShow); +void php_win_show_window(Int hWnd, Int cmdShow) { + ShowWindow((HWND)hWnd, (int)cmdShow); } -// Render game -void php_tetris_render(var box, Int hWnd) { - auto tetris = box.toBox(); - HDC hdc = GetDC((HWND)hWnd); - - // Clear background - RECT rect; - rect.left = 0; - rect.top = 0; - rect.right = BLOCK_SIZE * BOARD_WIDTH; - rect.bottom = BLOCK_SIZE * BOARD_HEIGHT; - FillRect(hdc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); - - // Draw board - for (int i = 0; i < BOARD_HEIGHT; i++) { - for (int j = 0; j < BOARD_WIDTH; j++) { - if (tetris->board[i][j]) { - HBRUSH brush = CreateSolidBrush(COLORS[tetris->board[i][j] - 1]); - RECT blockRect; - blockRect.left = j * BLOCK_SIZE; - blockRect.top = i * BLOCK_SIZE; - blockRect.right = (j + 1) * BLOCK_SIZE; - blockRect.bottom = (i + 1) * BLOCK_SIZE; - FillRect(hdc, &blockRect, brush); - DeleteObject(brush); - } - } - } - - ReleaseDC((HWND)hWnd, hdc); -} - -// Handle keyboard input -void php_tetris_handle_key(var box, Int keyCode) { - auto tetris = box.toBox(); - // Simplified: just increase score for testing - tetris->score += 1; +// Check if quit was requested (by WndProc) +Bool php_win_quit_requested() { + return g_quitRequested; } // Post quit message -void php_tetris_post_quit(Int exitCode) { +void php_win_post_quit(Int exitCode) { PostQuitMessage((int)exitCode); } -// Show message box with UTF-8 support -Int php_tetris_messagebox(Int hWnd, String text, String caption, Int uType) { +// PeekMessage wrapper - returns [hwnd, message, wParam, lParam] or empty array +Array php_win_peek_message() { + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + Array result; + result.append((Int)msg.hwnd); + result.append((Int)msg.message); + result.append((Int)msg.wParam); + result.append((Int)msg.lParam); + TranslateMessage(&msg); + DispatchMessage(&msg); + return result; + } + return Array(); +} + +// GetTickCount +Int php_win_get_tick_count() { + return (Int)GetTickCount(); +} + +// MessageBox with UTF-8 support +Int php_win_message_box(Int hWnd, String text, String caption, Int uType) { int wtext_len = MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, NULL, 0); wchar_t* wtext = new wchar_t[wtext_len]; MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, wtext, wtext_len); - + int wcaption_len = MultiByteToWideChar(CP_UTF8, 0, caption.data(), -1, NULL, 0); wchar_t* wcaption = new wchar_t[wcaption_len]; MultiByteToWideChar(CP_UTF8, 0, caption.data(), -1, wcaption, wcaption_len); - + int result = MessageBoxW((HWND)hWnd, wtext, wcaption, (UINT)uType); - + delete[] wtext; delete[] wcaption; return result; } + +// ============================================================ +// Win32 GDI Drawing Primitives +// ============================================================ + +// Begin a double-buffered frame. Returns memDC handle as int. +Int php_win_begin_paint(Int hWnd) { + HDC hdc = GetDC((HWND)hWnd); + RECT rc; + GetClientRect((HWND)hWnd, &rc); + + HDC memDC = CreateCompatibleDC(hdc); + HBITMAP memBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom); + SelectObject(memDC, memBitmap); + + // Release the screen DC now - we only needed it to create compatible objects + ReleaseDC((HWND)hWnd, hdc); + + return (Int)memDC; +} + +// End the double-buffered frame: blit back-buffer to screen and cleanup. +void php_win_end_paint(Int hWnd, Int hdcHandle) { + HDC memDC = (HDC)hdcHandle; + RECT rc; + GetClientRect((HWND)hWnd, &rc); + + // Blit memDC -> screen + HDC hdc = GetDC((HWND)hWnd); + BitBlt(hdc, 0, 0, rc.right, rc.bottom, memDC, 0, 0, SRCCOPY); + ReleaseDC((HWND)hWnd, hdc); + + // Cleanup memDC + DeleteDC(memDC); +} + +// Fill rectangle with RGB color +void php_win_fill_rect(Int hdc, Int x, Int y, Int w, Int h, Int rgbColor) { + HBRUSH brush = CreateSolidBrush((COLORREF)rgbColor); + RECT r = {(int)x, (int)y, (int)(x + w), (int)(y + h)}; + FillRect((HDC)hdc, &r, brush); + DeleteObject(brush); +} + +// Draw a colored block with border +void php_win_draw_block(Int hdc, Int x, Int y, Int size, Int rgbColor) { + COLORREF color = (COLORREF)rgbColor; + // Fill interior + HBRUSH brush = CreateSolidBrush(color); + RECT r = {(int)x + 1, (int)y + 1, (int)(x + size - 1), (int)(y + size - 1)}; + FillRect((HDC)hdc, &r, brush); + DeleteObject(brush); + // Draw border + HPEN borderPen = CreatePen(PS_SOLID, 1, RGB( + (BYTE)(GetRValue(color) * 0.6), + (BYTE)(GetGValue(color) * 0.6), + (BYTE)(GetBValue(color) * 0.6))); + HPEN oldPen = (HPEN)SelectObject((HDC)hdc, borderPen); + HBRUSH oldBrush = (HBRUSH)SelectObject((HDC)hdc, GetStockObject(NULL_BRUSH)); + Rectangle((HDC)hdc, (int)x, (int)y, (int)(x + size), (int)(y + size)); + SelectObject((HDC)hdc, oldBrush); + SelectObject((HDC)hdc, oldPen); + DeleteObject(borderPen); +} + +// Draw a line +void php_win_draw_line(Int hdc, Int x1, Int y1, Int x2, Int y2, Int rgbColor) { + HPEN pen = CreatePen(PS_SOLID, 1, (COLORREF)rgbColor); + HPEN oldPen = (HPEN)SelectObject((HDC)hdc, pen); + MoveToEx((HDC)hdc, (int)x1, (int)y1, NULL); + LineTo((HDC)hdc, (int)x2, (int)y2); + SelectObject((HDC)hdc, oldPen); + DeleteObject(pen); +} + +// Draw text at position with given font size and color +void php_win_draw_text(Int hdc, Int x, Int y, String text, Int fontSize, Int rgbColor, Int bold) { + SetTextColor((HDC)hdc, (COLORREF)rgbColor); + SetBkMode((HDC)hdc, TRANSPARENT); + HFONT hFont = CreateFont((int)fontSize, 0, 0, 0, + bold ? FW_BOLD : FW_NORMAL, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "Arial"); + HFONT oldFont = (HFONT)SelectObject((HDC)hdc, hFont); + TextOutA((HDC)hdc, (int)x, (int)y, text.data(), (int)strlen(text.data())); + SelectObject((HDC)hdc, oldFont); + DeleteObject(hFont); +} diff --git a/examples/tetris-win32/main.php b/examples/tetris-win32/main.php index fb4203f0..2c0daf18 100644 --- a/examples/tetris-win32/main.php +++ b/examples/tetris-win32/main.php @@ -1,234 +1,576 @@ board = array_fill(0, BOARD_HEIGHT, array_fill(0, BOARD_WIDTH, 0)); + $this->score = 0; + $this->gameOver = false; + $this->pieceType = random_int(0, 6); + $this->pieceRotation = 0; + $this->pieceX = 3; + $this->pieceY = 0; + $this->nextType = random_int(0, 6); + } + + public function reset(): void + { + $this->board = array_fill(0, BOARD_HEIGHT, array_fill(0, BOARD_WIDTH, 0)); + $this->score = 0; + $this->gameOver = false; + $this->pieceType = random_int(0, 6); + $this->pieceRotation = 0; + $this->pieceX = 3; + $this->pieceY = 0; + $this->nextType = random_int(0, 6); + } + + /** Check if the given piece at given position/rotation collides */ + public function collides(int $px, int $py, int $rot): bool + { + $shape = SHAPES[$this->pieceType][$rot]; + for ($i = 0; $i < 4; $i++) { + for ($j = 0; $j < 4; $j++) { + if ($shape[$i][$j]) { + $bx = $px + $j; + $by = $py + $i; + if ($bx < 0 || $bx >= BOARD_WIDTH || $by >= BOARD_HEIGHT) { + return true; + } + if ($by >= 0 && $this->board[$by][$bx] != 0) { + return true; + } + } + } + } + return false; + } + + /** Lock current piece onto the board */ + public function lockPiece(): void + { + $shape = SHAPES[$this->pieceType][$this->pieceRotation]; + for ($i = 0; $i < 4; $i++) { + for ($j = 0; $j < 4; $j++) { + if ($shape[$i][$j]) { + $bx = $this->pieceX + $j; + $by = $this->pieceY + $i; + if ($by >= 0 && $by < BOARD_HEIGHT && $bx >= 0 && $bx < BOARD_WIDTH) { + $this->board[$by][$bx] = $this->pieceType + 1; + } + } + } + } + } + + /** Clear completed lines, return number of lines cleared */ + public function clearLines(): int + { + $lines = 0; + for ($i = BOARD_HEIGHT - 1; $i >= 0; $i--) { + $full = true; + for ($j = 0; $j < BOARD_WIDTH; $j++) { + if ($this->board[$i][$j] == 0) { + $full = false; + break; + } + } + if ($full) { + $lines++; + // Shift rows down + for ($k = $i; $k > 0; $k--) { + for ($j = 0; $j < BOARD_WIDTH; $j++) { + $this->board[$k][$j] = $this->board[$k - 1][$j]; + } + } + for ($j = 0; $j < BOARD_WIDTH; $j++) { + $this->board[0][$j] = 0; + } + $i++; // recheck same row + } + } + return $lines; + } + + /** Spawn a new piece */ + public function spawnPiece(): void + { + $this->pieceType = $this->nextType; + $this->nextType = random_int(0, 6); + $this->pieceRotation = 0; + $this->pieceX = 3; + $this->pieceY = 0; + if ($this->collides($this->pieceX, $this->pieceY, $this->pieceRotation)) { + $this->gameOver = true; + } + } + + /** Add score for clearing lines */ + public function addLineScore(int $lines): void + { + $scores = [0, 100, 300, 500, 800]; + if ($lines > 0 && $lines <= 4) { + $this->score += $scores[$lines]; + } + } + + /** Move piece down. Returns true if moved, false if locked */ + public function moveDown(): bool + { + if ($this->gameOver) return false; + if (!$this->collides($this->pieceX, $this->pieceY + 1, $this->pieceRotation)) { + $this->pieceY++; + return true; + } + // Lock and spawn + $this->lockPiece(); + $lines = $this->clearLines(); + $this->addLineScore($lines); + $this->spawnPiece(); + return false; + } + + public function moveLeft(): bool + { + if ($this->gameOver) return false; + if (!$this->collides($this->pieceX - 1, $this->pieceY, $this->pieceRotation)) { + $this->pieceX--; + return true; + } + return false; + } + + public function moveRight(): bool + { + if ($this->gameOver) return false; + if (!$this->collides($this->pieceX + 1, $this->pieceY, $this->pieceRotation)) { + $this->pieceX++; + return true; + } + return false; + } + + public function rotate(): bool + { + if ($this->gameOver) return false; + $newRot = ($this->pieceRotation + 1) % 4; + if (!$this->collides($this->pieceX, $this->pieceY, $newRot)) { + $this->pieceRotation = $newRot; + return true; + } + return false; + } + + public function hardDrop(): void + { + if ($this->gameOver) return; + while (!$this->collides($this->pieceX, $this->pieceY + 1, $this->pieceRotation)) { + $this->pieceY++; + $this->score += 2; + } + $this->lockPiece(); + $lines = $this->clearLines(); + $this->addLineScore($lines); + $this->spawnPiece(); + } + + public function getLevel(): int + { + return intdiv($this->score, 500) + 1; + } +} + +// ============================================================ +// TetrisRenderer - Uses C++ drawing primitives +// ============================================================ + +class TetrisRenderer +{ + private int $hWnd; + + public function __construct(int $hWnd) + { + $this->hWnd = $hWnd; + } + + public function render(TetrisBoard $game): void + { + $hdc = win_begin_paint($this->hWnd); + $boardPxW = BLOCK_SIZE * BOARD_WIDTH; + $boardPxH = BLOCK_SIZE * BOARD_HEIGHT; + + // Clear background + win_fill_rect($hdc, 0, 0, $boardPxW + SIDEBAR_WIDTH, $boardPxH, rgb(0, 0, 0)); + + // Draw board (locked pieces) + for ($i = 0; $i < BOARD_HEIGHT; $i++) { + for ($j = 0; $j < BOARD_WIDTH; $j++) { + if ($game->board[$i][$j] != 0) { + $colorIdx = $game->board[$i][$j] - 1; + win_draw_block($hdc, $j * BLOCK_SIZE, $i * BLOCK_SIZE, BLOCK_SIZE, PIECE_COLORS[$colorIdx]); + } + } + } + + // Draw current piece + if (!$game->gameOver) { + $shape = SHAPES[$game->pieceType][$game->pieceRotation]; + for ($i = 0; $i < 4; $i++) { + for ($j = 0; $j < 4; $j++) { + if ($shape[$i][$j]) { + $bx = $game->pieceX + $j; + $by = $game->pieceY + $i; + if ($by >= 0 && $by < BOARD_HEIGHT && $bx >= 0 && $bx < BOARD_WIDTH) { + win_draw_block($hdc, $bx * BLOCK_SIZE, $by * BLOCK_SIZE, BLOCK_SIZE, PIECE_COLORS[$game->pieceType]); + } + } + } + } + } + + // Draw grid lines + $gridColor = rgb(40, 40, 40); + for ($i = 0; $i <= BOARD_HEIGHT; $i++) { + win_draw_line($hdc, 0, $i * BLOCK_SIZE, $boardPxW, $i * BLOCK_SIZE, $gridColor); + } + for ($j = 0; $j <= BOARD_WIDTH; $j++) { + win_draw_line($hdc, $j * BLOCK_SIZE, 0, $j * BLOCK_SIZE, $boardPxH, $gridColor); + } + + // Sidebar separator + win_draw_line($hdc, $boardPxW, 0, $boardPxW, $boardPxH, rgb(100, 100, 100)); + + // Sidebar content + $sx = $boardPxW + 10; + $white = rgb(255, 255, 255); + $gray = rgb(150, 150, 150); + + // SCORE + win_draw_text($hdc, $sx, 20, "SCORE", 24, $white, 1); + win_draw_text($hdc, $sx, 48, (string)$game->score, 28, $white, 1); + + // NEXT + win_draw_text($hdc, $sx, 110, "NEXT", 24, $white, 1); + $previewSize = 20; + $previewX = $sx + 10; + $previewY = 140; + $nextShape = SHAPES[$game->nextType][0]; + for ($i = 0; $i < 4; $i++) { + for ($j = 0; $j < 4; $j++) { + if ($nextShape[$i][$j]) { + win_draw_block($hdc, $previewX + $j * $previewSize, $previewY + $i * $previewSize, $previewSize, PIECE_COLORS[$game->nextType]); + } + } + } + + // LEVEL + win_draw_text($hdc, $sx, 230, "LEVEL", 24, $white, 1); + win_draw_text($hdc, $sx, 258, (string)$game->getLevel(), 28, $white, 1); + + // Controls + win_draw_text($hdc, $sx, 340, "Arrow/WASD: Move", 14, $gray, 0); + win_draw_text($hdc, $sx, 360, "Up/W: Rotate", 14, $gray, 0); + win_draw_text($hdc, $sx, 380, "Space: Drop", 14, $gray, 0); + + // Game Over overlay + if ($game->gameOver) { + win_fill_rect($hdc, 0, 0, $boardPxW, $boardPxH, rgb(0, 0, 0)); + // Center "GAME OVER" text + $goX = intdiv($boardPxW, 2) - 90; + $goY = intdiv($boardPxH, 2) - 30; + win_draw_text($hdc, $goX, $goY, "GAME OVER", 36, rgb(255, 50, 50), 1); + } + + win_end_paint($this->hWnd, $hdc); + } +} + +// ============================================================ +// TetrisGame - Main game controller +// ============================================================ -/** - * 俄罗斯方块游戏主类 - */ class TetrisGame { - private mixed $game; + private TetrisBoard $board; private int $hWnd; + private TetrisRenderer $renderer; private int $lastDropTime; private int $dropInterval; - + public function __construct() { - // 创建游戏实例(C++ Box 对象) - echo "正在创建游戏实例...\n"; - $this->game = tetris_new(); - echo "游戏实例已创建,类型: " . gettype($this->game) . "\n"; - - if (!is_resource($this->game) && !is_object($this->game)) { - echo "警告:game 不是有效的资源或对象类型\n"; - } - + $this->board = new TetrisBoard(); $this->hWnd = 0; $this->lastDropTime = 0; - $this->dropInterval = 500; // 初始下落间隔(毫秒) + $this->dropInterval = 1000; } - - /** - * 初始化游戏窗口 - */ + public function initWindow(): void { - echo "正在创建窗口...\n"; - $this->hWnd = tetris_create_window("俄罗斯方块 - PHP版"); - echo "窗口句柄: {$this->hWnd}\n"; - + $this->hWnd = win_create_window("Tetris - PHP AOT", WINDOW_WIDTH, WINDOW_HEIGHT); + if ($this->hWnd == 0) { - echo "错误:窗口创建失败!\n"; + echo "Error: window creation failed!\n"; return; } - - tetris_show_window($this->hWnd, SW_SHOW); - - echo "游戏窗口已创建\n"; - echo "控制说明:\n"; - echo " ← → : 左右移动\n"; - echo " ↑ : 旋转方块\n"; - echo " ↓ : 加速下落\n"; - echo " 空格 : 直接落下\n"; - echo "\n"; - } - - /** - * 游戏主循环 - */ + + win_show_window($this->hWnd, SW_SHOW); + $this->renderer = new TetrisRenderer($this->hWnd); + $this->lastDropTime = win_get_tick_count(); + echo "Window created\n"; + } + public function run(): void { - $msg = []; $running = true; - - echo "游戏开始!\n"; - + $frameCount = 0; + echo "Game started!\n"; + while ($running) { - // 处理 Windows 消息 - while (PeekMessage($msg, $this->hWnd, 0, 0, 1)) { - if (!isset($msg['message'])) { - continue; - } - - $messageType = $msg['message']; - - if ($messageType == WM_KEYDOWN) { - $keyCode = isset($msg['wParam']) ? $msg['wParam'] : 0; + // Process Windows messages + while (true) { + $msg = win_peek_message(); + if (count($msg) == 0) break; + + $msgType = $msg[1] ?? 0; + + if ($msgType == WM_KEYDOWN) { + $keyCode = $msg[2] ?? 0; $this->handleKeyPress($keyCode); } - - // 检查是否收到退出消息 - if ($messageType == 0x0012) { // WM_QUIT + + if ($msgType == WM_QUIT) { $running = false; break; } } - - if (!$running) { - break; + + if (win_quit_requested()) { + $running = false; } - - // 自动下落逻辑 - $currentTime = GetTickCount(); + if (!$running) break; + + // Auto drop + $currentTime = win_get_tick_count(); if ($currentTime - $this->lastDropTime > $this->dropInterval) { - if (!tetris_is_game_over($this->game)) { - tetris_move_down($this->game); - - // 根据分数调整速度 - $score = tetris_get_score($this->game); - $this->dropInterval = max(100, 500 - intdiv($score, 500) * 50); + if (!$this->board->gameOver) { + $this->board->moveDown(); + $this->dropInterval = max(200, 1000 - intdiv($this->board->score, 500) * 100); } $this->lastDropTime = $currentTime; } - - // 渲染游戏画面 - tetris_render($this->game, $this->hWnd); - - // 检查游戏结束 - if (tetris_is_game_over($this->game)) { + + // Render + $this->renderer->render($this->board); + + // Check game over + if ($this->board->gameOver) { $this->handleGameOver(); break; } - - // 控制帧率 - usleep(16000); // 约 60 FPS (16ms = 16000us) + + // Log every 60 frames + $frameCount++; + if ($frameCount % 60 == 0) { + echo "Frame {$frameCount}, score={$this->board->score}\n"; + } + + usleep(16000); // ~60 FPS } + + echo "Game ended\n"; } - - /** - * 处理键盘输入 - */ + private function handleKeyPress(int $keyCode): void { switch ($keyCode) { case VK_LEFT: - tetris_move_left($this->game); + case VK_A: + $this->board->moveLeft(); break; - case VK_RIGHT: - tetris_move_right($this->game); + case VK_D: + $this->board->moveRight(); break; - case VK_UP: - tetris_rotate($this->game); + case VK_W: + $this->board->rotate(); break; - case VK_DOWN: - tetris_move_down($this->game); + case VK_S: + $this->board->moveDown(); break; - case VK_SPACE: - tetris_hard_drop($this->game); + $this->board->hardDrop(); break; } } - - /** - * 处理游戏结束 - */ + private function handleGameOver(): void { - $score = tetris_get_score($this->game); - $message = "游戏结束!\n\n最终得分: {$score}\n\n是否重新开始?"; - - $result = tetris_messagebox( + $score = $this->board->score; + echo "Game Over! Score: {$score}\n"; + + $result = win_message_box( $this->hWnd, - $message, - "游戏结束", - MB_OK + "Game Over!\nScore: {$score}\nPlay again?", + "Game Over", + MB_YESNO ); - - if ($result == 1) { // IDOK - // 重新开始游戏 - tetris_reset($this->game); - $this->lastDropTime = GetTickCount(); - $this->dropInterval = 500; - echo "游戏重新开始\n"; + + if ($result == IDYES) { + $this->board->reset(); + $this->lastDropTime = win_get_tick_count(); + $this->dropInterval = 1000; } else { - echo "游戏退出\n"; - tetris_post_quit(0); + win_post_quit(0); } } - - /** - * 获取当前游戏状态 - */ - public function getStatus(): array - { - return [ - 'score' => tetris_get_score($this->game), - 'gameOver' => tetris_is_game_over($this->game), - 'board' => tetris_get_board($this->game), - 'currentPiece' => tetris_get_current_piece($this->game), - ]; - } } -/** - * 主函数 - */ +// ============================================================ +// Entry point +// ============================================================ + function main(): void { - // 设置时区 date_default_timezone_set('Asia/Shanghai'); - - // 设置控制台编码为 UTF-8(Windows) - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - exec('chcp 65001 > nul'); - } - + echo "========================================\n"; - echo " 俄罗斯方块 - PHP 编译器演示\n"; + echo " Tetris - PHP AOT Compiler Demo\n"; + echo " (Game logic 100% in PHP)\n"; echo "========================================\n\n"; - - // 创建并运行游戏 + $game = new TetrisGame(); $game->initWindow(); $game->run(); - - echo "\n感谢游玩!\n"; + + echo "\nThanks for playing!\n"; } diff --git a/examples/tetris-win32/php-src/tetris.stub.php b/examples/tetris-win32/php-src/tetris.stub.php index f460c020..a6c3162d 100644 --- a/examples/tetris-win32/php-src/tetris.stub.php +++ b/examples/tetris-win32/php-src/tetris.stub.php @@ -1,33 +1,24 @@