TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

576 lines
17 KiB

<?php
/**
* 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
*/
// ============================================================
// 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_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_QUIT = 0x0012;
// RGB helper
function rgb(int $r, int $g, int $b): int
{
return ($r | ($g << 8) | ($b << 16));
}
// 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
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
{
private TetrisBoard $board;
private int $hWnd;
private TetrisRenderer $renderer;
private int $lastDropTime;
private int $dropInterval;
public function __construct()
{
$this->board = new TetrisBoard();
$this->hWnd = 0;
$this->lastDropTime = 0;
$this->dropInterval = 1000;
}
public function initWindow(): void
{
$this->hWnd = win_create_window("Tetris - PHP AOT", WINDOW_WIDTH, WINDOW_HEIGHT);
if ($this->hWnd == 0) {
echo "Error: window creation failed!\n";
return;
}
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
{
$running = true;
$frameCount = 0;
echo "Game started!\n";
while ($running) {
// 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 ($msgType == WM_QUIT) {
$running = false;
break;
}
}
if (win_quit_requested()) {
$running = false;
}
if (!$running) break;
// Auto drop
$currentTime = win_get_tick_count();
if ($currentTime - $this->lastDropTime > $this->dropInterval) {
if (!$this->board->gameOver) {
$this->board->moveDown();
$this->dropInterval = max(200, 1000 - intdiv($this->board->score, 500) * 100);
}
$this->lastDropTime = $currentTime;
}
// Render
$this->renderer->render($this->board);
// Check game over
if ($this->board->gameOver) {
$this->handleGameOver();
break;
}
// 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:
case VK_A:
$this->board->moveLeft();
break;
case VK_RIGHT:
case VK_D:
$this->board->moveRight();
break;
case VK_UP:
case VK_W:
$this->board->rotate();
break;
case VK_DOWN:
case VK_S:
$this->board->moveDown();
break;
case VK_SPACE:
$this->board->hardDrop();
break;
}
}
private function handleGameOver(): void
{
$score = $this->board->score;
echo "Game Over! Score: {$score}\n";
$result = win_message_box(
$this->hWnd,
"Game Over!\nScore: {$score}\nPlay again?",
"Game Over",
MB_YESNO
);
if ($result == IDYES) {
$this->board->reset();
$this->lastDropTime = win_get_tick_count();
$this->dropInterval = 1000;
} else {
win_post_quit(0);
}
}
}
// ============================================================
// Entry point
// ============================================================
function main(): void
{
date_default_timezone_set('Asia/Shanghai');
echo "========================================\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 "\nThanks for playing!\n";
}