- 移除原有的C++游戏逻辑,将所有游戏状态管理转为PHP实现 - 添加TetrisBoard类处理游戏核心逻辑(碰撞检测、方块锁定、消除行等) - 添加TetrisRenderer类负责Win32图形渲染 - 定义完整的俄罗斯方块形状和颜色常量数组 - 实现键盘控制(左右移动、旋转、硬降等功能) - 添加计分系统和等级计算功能 - 更新C++层仅提供Win32 API封装(窗口创建、绘图原语等) - 修改游戏主循环处理Windows消息和自动下落逻辑 - 添加游戏结束检测和重新开始功能pull/1/head
parent
661ab07cd9
commit
6cc40059e0
4 changed files with 664 additions and 355 deletions
@ -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 <phpx.h> |
#include <phpx.h> |
||||||
#include <windows.h> |
#include <windows.h> |
||||||
#include <cstdlib> |
#include <cstdio> |
||||||
|
|
||||||
using namespace php; |
using namespace php; |
||||||
|
|
||||||
// Game constants
|
// ============================================================
|
||||||
#define BLOCK_SIZE 30 |
// Win32 Window & Message
|
||||||
#define BOARD_WIDTH 10 |
// ============================================================
|
||||||
#define BOARD_HEIGHT 20 |
|
||||||
|
static bool g_quitRequested = false; |
||||||
// Colors for each piece type
|
|
||||||
static const COLORREF COLORS[7] = { |
LRESULT CALLBACK TetrisWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { |
||||||
RGB(0, 255, 255), // I - Cyan
|
switch (msg) { |
||||||
RGB(255, 255, 0), // O - Yellow
|
case WM_CLOSE: |
||||||
RGB(128, 0, 128), // T - Purple
|
g_quitRequested = true; |
||||||
RGB(0, 255, 0), // S - Green
|
PostQuitMessage(0); |
||||||
RGB(255, 0, 0), // Z - Red
|
return 0; |
||||||
RGB(0, 0, 255), // J - Blue
|
case WM_DESTROY: |
||||||
RGB(255, 165, 0) // L - Orange
|
g_quitRequested = true; |
||||||
}; |
PostQuitMessage(0); |
||||||
|
return 0; |
||||||
// 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<TetrisBox>(); |
|
||||||
tetris->reset(); |
|
||||||
} |
|
||||||
|
|
||||||
// Get score
|
|
||||||
Int php_tetris_get_score(var box) { |
|
||||||
auto tetris = box.toBox<TetrisBox>(); |
|
||||||
return tetris->score; |
|
||||||
} |
|
||||||
|
|
||||||
// Check if game over
|
|
||||||
Bool php_tetris_is_game_over(var box) { |
|
||||||
auto tetris = box.toBox<TetrisBox>(); |
|
||||||
return tetris->gameOver; |
|
||||||
} |
|
||||||
|
|
||||||
// Move piece down
|
|
||||||
Bool php_tetris_move_down(var box) { |
|
||||||
auto tetris = box.toBox<TetrisBox>(); |
|
||||||
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<TetrisBox>(); |
|
||||||
if (tetris->gameOver) return false; |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
// Move piece right
|
|
||||||
Bool php_tetris_move_right(var box) { |
|
||||||
auto tetris = box.toBox<TetrisBox>(); |
|
||||||
if (tetris->gameOver) return false; |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
// Rotate piece
|
|
||||||
void php_tetris_rotate(var box) { |
|
||||||
auto tetris = box.toBox<TetrisBox>(); |
|
||||||
if (!tetris->gameOver) { |
|
||||||
tetris->score += 5; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Hard drop
|
|
||||||
void php_tetris_hard_drop(var box) { |
|
||||||
auto tetris = box.toBox<TetrisBox>(); |
|
||||||
if (!tetris->gameOver) { |
|
||||||
tetris->score += 50; |
|
||||||
} |
} |
||||||
|
return DefWindowProc(hWnd, msg, wParam, lParam); |
||||||
} |
} |
||||||
|
|
||||||
// Get board state
|
// Create window, returns hWnd
|
||||||
Array php_tetris_get_board(var box) { |
Int php_win_create_window(String title, Int width, Int height) { |
||||||
auto tetris = box.toBox<TetrisBox>(); |
SetConsoleOutputCP(65001); |
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
// 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; |
WNDCLASS wc; |
||||||
ZeroMemory(&wc, sizeof(wc)); |
ZeroMemory(&wc, sizeof(wc)); |
||||||
wc.style = CS_HREDRAW | CS_VREDRAW; |
wc.style = CS_HREDRAW | CS_VREDRAW; |
||||||
wc.lpfnWndProc = DefWindowProc; |
wc.lpfnWndProc = TetrisWndProc; |
||||||
wc.hInstance = GetModuleHandle(NULL); |
wc.hInstance = GetModuleHandle(NULL); |
||||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW); |
wc.hCursor = LoadCursor(NULL, IDC_ARROW); |
||||||
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); |
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); |
||||||
wc.lpszClassName = "TetrisWindow"; |
wc.lpszClassName = "TetrisWindow"; |
||||||
|
|
||||||
RegisterClass(&wc); |
RegisterClass(&wc); |
||||||
|
|
||||||
HWND hWnd = CreateWindowEx( |
HWND hWnd = CreateWindowEx( |
||||||
0, |
0, "TetrisWindow", title.data(), |
||||||
"TetrisWindow", |
|
||||||
title.data(), |
|
||||||
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, |
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, |
||||||
CW_USEDEFAULT, |
CW_USEDEFAULT, CW_USEDEFAULT, |
||||||
CW_USEDEFAULT, |
(int)width, (int)height, |
||||||
BLOCK_SIZE * BOARD_WIDTH + 200, |
NULL, NULL, GetModuleHandle(NULL), NULL |
||||||
BLOCK_SIZE * BOARD_HEIGHT + 40, |
|
||||||
NULL, |
|
||||||
NULL, |
|
||||||
GetModuleHandle(NULL), |
|
||||||
NULL |
|
||||||
); |
); |
||||||
|
|
||||||
return (Int)hWnd; |
return (Int)hWnd; |
||||||
} |
} |
||||||
|
|
||||||
// Show window
|
// Show window
|
||||||
Bool php_tetris_show_window(Int hWnd, Int cmdShow) { |
void php_win_show_window(Int hWnd, Int cmdShow) { |
||||||
return ShowWindow((HWND)hWnd, (int)cmdShow); |
ShowWindow((HWND)hWnd, (int)cmdShow); |
||||||
} |
} |
||||||
|
|
||||||
// Render game
|
// Check if quit was requested (by WndProc)
|
||||||
void php_tetris_render(var box, Int hWnd) { |
Bool php_win_quit_requested() { |
||||||
auto tetris = box.toBox<TetrisBox>(); |
return g_quitRequested; |
||||||
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<TetrisBox>(); |
|
||||||
// Simplified: just increase score for testing
|
|
||||||
tetris->score += 1; |
|
||||||
} |
} |
||||||
|
|
||||||
// Post quit message
|
// Post quit message
|
||||||
void php_tetris_post_quit(Int exitCode) { |
void php_win_post_quit(Int exitCode) { |
||||||
PostQuitMessage((int)exitCode); |
PostQuitMessage((int)exitCode); |
||||||
} |
} |
||||||
|
|
||||||
// Show message box with UTF-8 support
|
// PeekMessage wrapper - returns [hwnd, message, wParam, lParam] or empty array
|
||||||
Int php_tetris_messagebox(Int hWnd, String text, String caption, Int uType) { |
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); |
int wtext_len = MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, NULL, 0); |
||||||
wchar_t* wtext = new wchar_t[wtext_len]; |
wchar_t* wtext = new wchar_t[wtext_len]; |
||||||
MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, wtext, wtext_len); |
MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, wtext, wtext_len); |
||||||
|
|
||||||
int wcaption_len = MultiByteToWideChar(CP_UTF8, 0, caption.data(), -1, NULL, 0); |
int wcaption_len = MultiByteToWideChar(CP_UTF8, 0, caption.data(), -1, NULL, 0); |
||||||
wchar_t* wcaption = new wchar_t[wcaption_len]; |
wchar_t* wcaption = new wchar_t[wcaption_len]; |
||||||
MultiByteToWideChar(CP_UTF8, 0, caption.data(), -1, wcaption, wcaption_len); |
MultiByteToWideChar(CP_UTF8, 0, caption.data(), -1, wcaption, wcaption_len); |
||||||
|
|
||||||
int result = MessageBoxW((HWND)hWnd, wtext, wcaption, (UINT)uType); |
int result = MessageBoxW((HWND)hWnd, wtext, wcaption, (UINT)uType); |
||||||
|
|
||||||
delete[] wtext; |
delete[] wtext; |
||||||
delete[] wcaption; |
delete[] wcaption; |
||||||
return result; |
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); |
||||||
|
} |
||||||
|
|||||||
@ -1,234 +1,576 @@ |
|||||||
<?php |
<?php |
||||||
|
|
||||||
/** |
/** |
||||||
* Tetris Game - Main Logic in PHP |
* Tetris Game - Pure PHP Logic + Win32 C++ Drawing Primitives |
||||||
* Using C++ API for graphics and game state management |
* |
||||||
|
* This project demonstrates the AOT compiler's capability: |
||||||
|
* - C++ only wraps Win32 APIs (window, message, GDI drawing) |
||||||
|
* - ALL game logic is implemented in PHP |
||||||
*/ |
*/ |
||||||
|
|
||||||
// Windows 常量定义 |
// ============================================================ |
||||||
|
// Constants |
||||||
|
// ============================================================ |
||||||
|
|
||||||
|
const BLOCK_SIZE = 30; |
||||||
|
const BOARD_WIDTH = 10; |
||||||
|
const BOARD_HEIGHT = 20; |
||||||
|
const SIDEBAR_WIDTH = 180; |
||||||
|
const WINDOW_WIDTH = BLOCK_SIZE * BOARD_WIDTH + SIDEBAR_WIDTH + 16; |
||||||
|
const WINDOW_HEIGHT = BLOCK_SIZE * BOARD_HEIGHT + 40; |
||||||
|
|
||||||
|
// Win32 constants |
||||||
const SW_SHOW = 5; |
const SW_SHOW = 5; |
||||||
const MB_OK = 0x00000000; |
const MB_YESNO = 0x00000004; |
||||||
|
const IDYES = 6; |
||||||
const VK_LEFT = 0x25; |
const VK_LEFT = 0x25; |
||||||
const VK_RIGHT = 0x27; |
const VK_RIGHT = 0x27; |
||||||
const VK_UP = 0x26; |
const VK_UP = 0x26; |
||||||
const VK_DOWN = 0x28; |
const VK_DOWN = 0x28; |
||||||
const VK_SPACE = 0x20; |
const VK_SPACE = 0x20; |
||||||
|
const VK_W = 0x57; |
||||||
|
const VK_A = 0x41; |
||||||
|
const VK_S = 0x53; |
||||||
|
const VK_D = 0x44; |
||||||
const WM_KEYDOWN = 0x0100; |
const WM_KEYDOWN = 0x0100; |
||||||
const WM_PAINT = 0x000F; |
const WM_QUIT = 0x0012; |
||||||
|
|
||||||
function GetMessage(array &$lpMsg, int $hWnd, int $wMsgFilterMin, int $wMsgFilterMax): int {} |
// RGB helper |
||||||
|
function rgb(int $r, int $g, int $b): int |
||||||
|
{ |
||||||
|
return ($r | ($g << 8) | ($b << 16)); |
||||||
|
} |
||||||
|
|
||||||
function TranslateMessage(array $lpMsg): int {} |
// Piece colors |
||||||
|
const COLOR_CYAN = 0x00FFFF; // I |
||||||
|
const COLOR_YELLOW = 0x00FFFF; // O - will override below |
||||||
|
const COLOR_PURPLE = 0x800080; // T |
||||||
|
const COLOR_GREEN = 0x00FF00; // S |
||||||
|
const COLOR_RED = 0x0000FF; // Z |
||||||
|
const COLOR_BLUE = 0xFF0000; // J |
||||||
|
const COLOR_ORANGE = 0x00A5FF; // L |
||||||
|
|
||||||
function DispatchMessage(array $lpMsg): int {} |
const PIECE_COLORS = [ |
||||||
|
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 |
||||||
|
]; |
||||||
|
|
||||||
function PeekMessage(array &$lpMsg, int $hWnd, int $wMsgFilterMin, int $wMsgFilterMax, int $wRemoveMsg): int {} |
// 7 tetromino shapes (4 rotations each, 4x4 grid) - defined in PHP! |
||||||
|
const SHAPES = [ |
||||||
|
// I |
||||||
|
[ |
||||||
|
[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], |
||||||
|
[[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]], |
||||||
|
[[0,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,0,0]], |
||||||
|
[[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]], |
||||||
|
], |
||||||
|
// O |
||||||
|
[ |
||||||
|
[[0,0,0,0],[0,1,1,0],[0,1,1,0],[0,0,0,0]], |
||||||
|
[[0,0,0,0],[0,1,1,0],[0,1,1,0],[0,0,0,0]], |
||||||
|
[[0,0,0,0],[0,1,1,0],[0,1,1,0],[0,0,0,0]], |
||||||
|
[[0,0,0,0],[0,1,1,0],[0,1,1,0],[0,0,0,0]], |
||||||
|
], |
||||||
|
// T |
||||||
|
[ |
||||||
|
[[0,0,0,0],[0,1,0,0],[1,1,1,0],[0,0,0,0]], |
||||||
|
[[0,0,0,0],[0,1,0,0],[0,1,1,0],[0,1,0,0]], |
||||||
|
[[0,0,0,0],[0,0,0,0],[1,1,1,0],[0,1,0,0]], |
||||||
|
[[0,0,0,0],[0,1,0,0],[1,1,0,0],[0,1,0,0]], |
||||||
|
], |
||||||
|
// S |
||||||
|
[ |
||||||
|
[[0,0,0,0],[0,1,1,0],[1,1,0,0],[0,0,0,0]], |
||||||
|
[[0,0,0,0],[0,1,0,0],[0,1,1,0],[0,0,1,0]], |
||||||
|
[[0,0,0,0],[0,0,0,0],[0,1,1,0],[1,1,0,0]], |
||||||
|
[[0,0,0,0],[1,0,0,0],[1,1,0,0],[0,1,0,0]], |
||||||
|
], |
||||||
|
// Z |
||||||
|
[ |
||||||
|
[[0,0,0,0],[1,1,0,0],[0,1,1,0],[0,0,0,0]], |
||||||
|
[[0,0,0,0],[0,0,1,0],[0,1,1,0],[0,1,0,0]], |
||||||
|
[[0,0,0,0],[0,0,0,0],[1,1,0,0],[0,1,1,0]], |
||||||
|
[[0,0,0,0],[0,1,0,0],[1,1,0,0],[1,0,0,0]], |
||||||
|
], |
||||||
|
// J |
||||||
|
[ |
||||||
|
[[0,0,0,0],[1,0,0,0],[1,1,1,0],[0,0,0,0]], |
||||||
|
[[0,0,0,0],[0,1,1,0],[0,1,0,0],[0,1,0,0]], |
||||||
|
[[0,0,0,0],[0,0,0,0],[1,1,1,0],[0,0,1,0]], |
||||||
|
[[0,0,0,0],[0,1,0,0],[0,1,0,0],[1,1,0,0]], |
||||||
|
], |
||||||
|
// L |
||||||
|
[ |
||||||
|
[[0,0,0,0],[0,0,1,0],[1,1,1,0],[0,0,0,0]], |
||||||
|
[[0,0,0,0],[0,1,0,0],[0,1,0,0],[0,1,1,0]], |
||||||
|
[[0,0,0,0],[0,0,0,0],[1,1,1,0],[1,0,0,0]], |
||||||
|
[[0,0,0,0],[1,1,0,0],[0,1,0,0],[0,1,0,0]], |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
function GetTickCount(): int {} |
// ============================================================ |
||||||
|
// TetrisBoard - Pure PHP game logic |
||||||
|
// ============================================================ |
||||||
|
|
||||||
|
class TetrisBoard |
||||||
|
{ |
||||||
|
public array $board; // 20x10 grid, 0=empty, 1-7=piece type |
||||||
|
public int $score; |
||||||
|
public bool $gameOver; |
||||||
|
public int $pieceType; // 0-6 |
||||||
|
public int $pieceRotation; // 0-3 |
||||||
|
public int $pieceX; // column |
||||||
|
public int $pieceY; // row |
||||||
|
public int $nextType; // next piece type |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$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); |
||||||
|
} |
||||||
|
|
||||||
|
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 |
class TetrisGame |
||||||
{ |
{ |
||||||
private mixed $game; |
private TetrisBoard $board; |
||||||
private int $hWnd; |
private int $hWnd; |
||||||
|
private TetrisRenderer $renderer; |
||||||
private int $lastDropTime; |
private int $lastDropTime; |
||||||
private int $dropInterval; |
private int $dropInterval; |
||||||
|
|
||||||
public function __construct() |
public function __construct() |
||||||
{ |
{ |
||||||
// 创建游戏实例(C++ Box 对象) |
$this->board = new TetrisBoard(); |
||||||
echo "正在创建游戏实例...\n"; |
|
||||||
$this->game = tetris_new(); |
|
||||||
echo "游戏实例已创建,类型: " . gettype($this->game) . "\n"; |
|
||||||
|
|
||||||
if (!is_resource($this->game) && !is_object($this->game)) { |
|
||||||
echo "警告:game 不是有效的资源或对象类型\n"; |
|
||||||
} |
|
||||||
|
|
||||||
$this->hWnd = 0; |
$this->hWnd = 0; |
||||||
$this->lastDropTime = 0; |
$this->lastDropTime = 0; |
||||||
$this->dropInterval = 500; // 初始下落间隔(毫秒) |
$this->dropInterval = 1000; |
||||||
} |
} |
||||||
|
|
||||||
/** |
|
||||||
* 初始化游戏窗口 |
|
||||||
*/ |
|
||||||
public function initWindow(): void |
public function initWindow(): void |
||||||
{ |
{ |
||||||
echo "正在创建窗口...\n"; |
$this->hWnd = win_create_window("Tetris - PHP AOT", WINDOW_WIDTH, WINDOW_HEIGHT); |
||||||
$this->hWnd = tetris_create_window("俄罗斯方块 - PHP版"); |
|
||||||
echo "窗口句柄: {$this->hWnd}\n"; |
|
||||||
|
|
||||||
if ($this->hWnd == 0) { |
if ($this->hWnd == 0) { |
||||||
echo "错误:窗口创建失败!\n"; |
echo "Error: window creation failed!\n"; |
||||||
return; |
return; |
||||||
} |
} |
||||||
|
|
||||||
tetris_show_window($this->hWnd, SW_SHOW); |
win_show_window($this->hWnd, SW_SHOW); |
||||||
|
$this->renderer = new TetrisRenderer($this->hWnd); |
||||||
echo "游戏窗口已创建\n"; |
$this->lastDropTime = win_get_tick_count(); |
||||||
echo "控制说明:\n"; |
echo "Window created\n"; |
||||||
echo " ← → : 左右移动\n"; |
} |
||||||
echo " ↑ : 旋转方块\n"; |
|
||||||
echo " ↓ : 加速下落\n"; |
|
||||||
echo " 空格 : 直接落下\n"; |
|
||||||
echo "\n"; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 游戏主循环 |
|
||||||
*/ |
|
||||||
public function run(): void |
public function run(): void |
||||||
{ |
{ |
||||||
$msg = []; |
|
||||||
$running = true; |
$running = true; |
||||||
|
$frameCount = 0; |
||||||
echo "游戏开始!\n"; |
echo "Game started!\n"; |
||||||
|
|
||||||
while ($running) { |
while ($running) { |
||||||
// 处理 Windows 消息 |
// Process Windows messages |
||||||
while (PeekMessage($msg, $this->hWnd, 0, 0, 1)) { |
while (true) { |
||||||
if (!isset($msg['message'])) { |
$msg = win_peek_message(); |
||||||
continue; |
if (count($msg) == 0) break; |
||||||
} |
|
||||||
|
$msgType = $msg[1] ?? 0; |
||||||
$messageType = $msg['message']; |
|
||||||
|
if ($msgType == WM_KEYDOWN) { |
||||||
if ($messageType == WM_KEYDOWN) { |
$keyCode = $msg[2] ?? 0; |
||||||
$keyCode = isset($msg['wParam']) ? $msg['wParam'] : 0; |
|
||||||
$this->handleKeyPress($keyCode); |
$this->handleKeyPress($keyCode); |
||||||
} |
} |
||||||
|
|
||||||
// 检查是否收到退出消息 |
if ($msgType == WM_QUIT) { |
||||||
if ($messageType == 0x0012) { // WM_QUIT |
|
||||||
$running = false; |
$running = false; |
||||||
break; |
break; |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
if (!$running) { |
if (win_quit_requested()) { |
||||||
break; |
$running = false; |
||||||
} |
} |
||||||
|
if (!$running) break; |
||||||
// 自动下落逻辑 |
|
||||||
$currentTime = GetTickCount(); |
// Auto drop |
||||||
|
$currentTime = win_get_tick_count(); |
||||||
if ($currentTime - $this->lastDropTime > $this->dropInterval) { |
if ($currentTime - $this->lastDropTime > $this->dropInterval) { |
||||||
if (!tetris_is_game_over($this->game)) { |
if (!$this->board->gameOver) { |
||||||
tetris_move_down($this->game); |
$this->board->moveDown(); |
||||||
|
$this->dropInterval = max(200, 1000 - intdiv($this->board->score, 500) * 100); |
||||||
// 根据分数调整速度 |
|
||||||
$score = tetris_get_score($this->game); |
|
||||||
$this->dropInterval = max(100, 500 - intdiv($score, 500) * 50); |
|
||||||
} |
} |
||||||
$this->lastDropTime = $currentTime; |
$this->lastDropTime = $currentTime; |
||||||
} |
} |
||||||
|
|
||||||
// 渲染游戏画面 |
// Render |
||||||
tetris_render($this->game, $this->hWnd); |
$this->renderer->render($this->board); |
||||||
|
|
||||||
// 检查游戏结束 |
// Check game over |
||||||
if (tetris_is_game_over($this->game)) { |
if ($this->board->gameOver) { |
||||||
$this->handleGameOver(); |
$this->handleGameOver(); |
||||||
break; |
break; |
||||||
} |
} |
||||||
|
|
||||||
// 控制帧率 |
// Log every 60 frames |
||||||
usleep(16000); // 约 60 FPS (16ms = 16000us) |
$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 |
private function handleKeyPress(int $keyCode): void |
||||||
{ |
{ |
||||||
switch ($keyCode) { |
switch ($keyCode) { |
||||||
case VK_LEFT: |
case VK_LEFT: |
||||||
tetris_move_left($this->game); |
case VK_A: |
||||||
|
$this->board->moveLeft(); |
||||||
break; |
break; |
||||||
|
|
||||||
case VK_RIGHT: |
case VK_RIGHT: |
||||||
tetris_move_right($this->game); |
case VK_D: |
||||||
|
$this->board->moveRight(); |
||||||
break; |
break; |
||||||
|
|
||||||
case VK_UP: |
case VK_UP: |
||||||
tetris_rotate($this->game); |
case VK_W: |
||||||
|
$this->board->rotate(); |
||||||
break; |
break; |
||||||
|
|
||||||
case VK_DOWN: |
case VK_DOWN: |
||||||
tetris_move_down($this->game); |
case VK_S: |
||||||
|
$this->board->moveDown(); |
||||||
break; |
break; |
||||||
|
|
||||||
case VK_SPACE: |
case VK_SPACE: |
||||||
tetris_hard_drop($this->game); |
$this->board->hardDrop(); |
||||||
break; |
break; |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
/** |
|
||||||
* 处理游戏结束 |
|
||||||
*/ |
|
||||||
private function handleGameOver(): void |
private function handleGameOver(): void |
||||||
{ |
{ |
||||||
$score = tetris_get_score($this->game); |
$score = $this->board->score; |
||||||
$message = "游戏结束!\n\n最终得分: {$score}\n\n是否重新开始?"; |
echo "Game Over! Score: {$score}\n"; |
||||||
|
|
||||||
$result = tetris_messagebox( |
$result = win_message_box( |
||||||
$this->hWnd, |
$this->hWnd, |
||||||
$message, |
"Game Over!\nScore: {$score}\nPlay again?", |
||||||
"游戏结束", |
"Game Over", |
||||||
MB_OK |
MB_YESNO |
||||||
); |
); |
||||||
|
|
||||||
if ($result == 1) { // IDOK |
if ($result == IDYES) { |
||||||
// 重新开始游戏 |
$this->board->reset(); |
||||||
tetris_reset($this->game); |
$this->lastDropTime = win_get_tick_count(); |
||||||
$this->lastDropTime = GetTickCount(); |
$this->dropInterval = 1000; |
||||||
$this->dropInterval = 500; |
|
||||||
echo "游戏重新开始\n"; |
|
||||||
} else { |
} else { |
||||||
echo "游戏退出\n"; |
win_post_quit(0); |
||||||
tetris_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 |
function main(): void |
||||||
{ |
{ |
||||||
// 设置时区 |
|
||||||
date_default_timezone_set('Asia/Shanghai'); |
date_default_timezone_set('Asia/Shanghai'); |
||||||
|
|
||||||
// 设置控制台编码为 UTF-8(Windows) |
|
||||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
|
||||||
exec('chcp 65001 > nul'); |
|
||||||
} |
|
||||||
|
|
||||||
echo "========================================\n"; |
echo "========================================\n"; |
||||||
echo " 俄罗斯方块 - PHP 编译器演示\n"; |
echo " Tetris - PHP AOT Compiler Demo\n"; |
||||||
|
echo " (Game logic 100% in PHP)\n"; |
||||||
echo "========================================\n\n"; |
echo "========================================\n\n"; |
||||||
|
|
||||||
// 创建并运行游戏 |
|
||||||
$game = new TetrisGame(); |
$game = new TetrisGame(); |
||||||
$game->initWindow(); |
$game->initWindow(); |
||||||
$game->run(); |
$game->run(); |
||||||
|
|
||||||
echo "\n感谢游玩!\n"; |
echo "\nThanks for playing!\n"; |
||||||
} |
} |
||||||
|
|||||||
@ -1,33 +1,24 @@ |
|||||||
<?php |
<?php |
||||||
|
|
||||||
/** |
/** |
||||||
* Tetris Game C++ API declarations (stub) |
* Win32 API declarations (stub) |
||||||
* These functions are implemented in C++, PHP layer only declares them |
* C++ only provides thin wrappers around Win32 APIs. |
||||||
|
* ALL game logic is implemented in PHP. |
||||||
*/ |
*/ |
||||||
|
|
||||||
// 游戏控制函数 |
// Window management |
||||||
function tetris_new(): mixed {} |
function win_create_window(string $title, int $width, int $height): int {} |
||||||
function tetris_reset(mixed $game): void {} |
function win_show_window(int $hWnd, int $cmdShow): void {} |
||||||
function tetris_get_score(mixed $game): int {} |
function win_quit_requested(): bool {} |
||||||
function tetris_is_game_over(mixed $game): bool {} |
function win_post_quit(int $exitCode): void {} |
||||||
|
function win_peek_message(): array {} |
||||||
|
function win_get_tick_count(): int {} |
||||||
|
function win_message_box(int $hWnd, string $text, string $caption, int $uType): int {} |
||||||
|
|
||||||
// 方块移动函数 |
// GDI drawing primitives |
||||||
function tetris_rotate(mixed $game): void {} |
function win_begin_paint(int $hWnd): int {} |
||||||
function tetris_move_down(mixed $game): bool {} |
function win_end_paint(int $hWnd, int $hdc): void {} |
||||||
function tetris_move_left(mixed $game): bool {} |
function win_fill_rect(int $hdc, int $x, int $y, int $w, int $h, int $rgb): void {} |
||||||
function tetris_move_right(mixed $game): bool {} |
function win_draw_block(int $hdc, int $x, int $y, int $size, int $rgb): void {} |
||||||
function tetris_hard_drop(mixed $game): void {} |
function win_draw_line(int $hdc, int $x1, int $y1, int $x2, int $y2, int $rgb): void {} |
||||||
|
function win_draw_text(int $hdc, int $x, int $y, string $text, int $fontSize, int $rgb, int $bold): void {} |
||||||
// 获取游戏状态 |
|
||||||
function tetris_get_board(mixed $game): array {} |
|
||||||
function tetris_get_current_piece(mixed $game): array {} |
|
||||||
|
|
||||||
// Windows 窗口函数 |
|
||||||
function tetris_create_window(string $title): int {} |
|
||||||
function tetris_show_window(int $hWnd, int $cmdShow): bool {} |
|
||||||
function tetris_render(mixed $game, int $hWnd): void {} |
|
||||||
function tetris_handle_key(mixed $game, int $keyCode): void {} |
|
||||||
|
|
||||||
// 工具函数 |
|
||||||
function tetris_messagebox(int $hWnd, string $text, string $caption, int $uType): int {} |
|
||||||
function tetris_post_quit(int $exitCode): void {} |
|
||||||
|
|||||||
@ -1 +1 @@ |
|||||||
1035 |
1035 |
||||||
Loading…
Reference in new issue