feat(game): 将俄罗斯方块游戏重构为纯PHP逻辑实现

- 移除原有的C++游戏逻辑,将所有游戏状态管理转为PHP实现
- 添加TetrisBoard类处理游戏核心逻辑(碰撞检测、方块锁定、消除行等)
- 添加TetrisRenderer类负责Win32图形渲染
- 定义完整的俄罗斯方块形状和颜色常量数组
- 实现键盘控制(左右移动、旋转、硬降等功能)
- 添加计分系统和等级计算功能
- 更新C++层仅提供Win32 API封装(窗口创建、绘图原语等)
- 修改游戏主循环处理Windows消息和自动下落逻辑
- 添加游戏结束检测和重新开始功能
pull/1/head
韩天峰 4 months ago
parent 661ab07cd9
commit 6cc40059e0
  1. 324
      examples/tetris-win32/cpp-src/tetris.cc
  2. 594
      examples/tetris-win32/main.php
  3. 45
      examples/tetris-win32/php-src/tetris.stub.php
  4. 2
      version.txt

@ -1,211 +1,99 @@
/**
* 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 <windows.h>
#include <cstdlib>
#include <cstdio>
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<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;
// ============================================================
// 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);
}
// Hard drop
void php_tetris_hard_drop(var box) {
auto tetris = box.toBox<TetrisBox>();
if (!tetris->gameOver) {
tetris->score += 50;
}
}
// Create window, returns hWnd
Int php_win_create_window(String title, Int width, Int height) {
SetConsoleOutputCP(65001);
// Get board state
Array php_tetris_get_board(var box) {
auto tetris = box.toBox<TetrisBox>();
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;
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<TetrisBox>();
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);
}
}
}
// Check if quit was requested (by WndProc)
Bool php_win_quit_requested() {
return g_quitRequested;
}
ReleaseDC((HWND)hWnd, hdc);
// Post quit message
void php_win_post_quit(Int exitCode) {
PostQuitMessage((int)exitCode);
}
// 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;
// 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();
}
// Post quit message
void php_tetris_post_quit(Int exitCode) {
PostQuitMessage((int)exitCode);
// GetTickCount
Int php_win_get_tick_count() {
return (Int)GetTickCount();
}
// Show message box with UTF-8 support
Int php_tetris_messagebox(Int hWnd, String text, String caption, Int uType) {
// 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);
@ -220,3 +108,91 @@ Int php_tetris_messagebox(Int hWnd, String text, String caption, Int uType) {
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);
}

@ -1,234 +1,576 @@
<?php
/**
* Tetris Game - Main Logic in PHP
* Using C++ API for graphics and game state management
* Tetris Game - Pure PHP Logic + Win32 C++ Drawing Primitives
*
* 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 MB_OK = 0x00000000;
const MB_YESNO = 0x00000004;
const IDYES = 6;
const VK_LEFT = 0x25;
const VK_RIGHT = 0x27;
const VK_UP = 0x26;
const VK_DOWN = 0x28;
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_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
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
];
// 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]],
],
];
// ============================================================
// 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
function DispatchMessage(array $lpMsg): int {}
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);
}
function PeekMessage(array &$lpMsg, int $hWnd, int $wMsgFilterMin, int $wMsgFilterMax, int $wRemoveMsg): int {}
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);
}
function GetTickCount(): int {}
/** 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;
}
// Process Windows messages
while (true) {
$msg = win_peek_message();
if (count($msg) == 0) break;
$messageType = $msg['message'];
$msgType = $msg[1] ?? 0;
if ($messageType == WM_KEYDOWN) {
$keyCode = isset($msg['wParam']) ? $msg['wParam'] : 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;
}
if (!$running) break;
// 自动下落逻辑
$currentTime = GetTickCount();
// 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);
// Render
$this->renderer->render($this->board);
// 检查游戏结束
if (tetris_is_game_over($this->game)) {
// 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是否重新开始?";
$score = $this->board->score;
echo "Game Over! Score: {$score}\n";
$result = tetris_messagebox(
$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";
}

@ -1,33 +1,24 @@
<?php
/**
* Tetris Game C++ API declarations (stub)
* These functions are implemented in C++, PHP layer only declares them
* Win32 API declarations (stub)
* C++ only provides thin wrappers around Win32 APIs.
* ALL game logic is implemented in PHP.
*/
// 游戏控制函数
function tetris_new(): mixed {}
function tetris_reset(mixed $game): void {}
function tetris_get_score(mixed $game): int {}
function tetris_is_game_over(mixed $game): bool {}
// Window management
function win_create_window(string $title, int $width, int $height): int {}
function win_show_window(int $hWnd, int $cmdShow): void {}
function win_quit_requested(): 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 {}
// 方块移动函数
function tetris_rotate(mixed $game): void {}
function tetris_move_down(mixed $game): bool {}
function tetris_move_left(mixed $game): bool {}
function tetris_move_right(mixed $game): bool {}
function tetris_hard_drop(mixed $game): 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 {}
// GDI drawing primitives
function win_begin_paint(int $hWnd): int {}
function win_end_paint(int $hWnd, int $hdc): void {}
function win_fill_rect(int $hdc, int $x, int $y, int $w, int $h, int $rgb): void {}
function win_draw_block(int $hdc, int $x, int $y, int $size, int $rgb): 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 {}

@ -1 +1 @@
1035
1035
Loading…
Cancel
Save