feat: 添加斗地主Win32示例游戏

pull/15/head
韩天峰 2 months ago
parent 91af3ea0ea
commit 9ffe109cb1
  1. 205
      examples/landlord-win32/cpp-src/win32.cc
  2. 1540
      examples/landlord-win32/main.php
  3. 258
      examples/landlord-win32/php-src/card_rules.php
  4. 16
      examples/landlord-win32/php-src/win32.stub.php
  5. 7
      examples/landlord-win32/project.yml
  6. 10
      package-on-windows.php
  7. 2
      version.txt

@ -0,0 +1,205 @@
/**
* Landlord Win32 API Layer
*
* C++ is intentionally limited to Win32 windowing and primitive drawing.
* The game rules and AI live in PHP.
*/
#include <phpx.h>
#include <windows.h>
#include <cstdio>
#include <cstring>
#include <cwchar>
using namespace php;
static bool g_quitRequested = false;
struct PaintFrame {
HDC memDC;
HBITMAP bitmap;
HBITMAP oldBitmap;
};
static HDC frame_hdc(Int frameHandle) {
PaintFrame* frame = (PaintFrame*)frameHandle;
return frame ? frame->memDC : NULL;
}
LRESULT CALLBACK LandlordWndProc(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);
}
Int php_win_create_window(String title, Int width, Int height) {
SetConsoleOutputCP(65001);
WNDCLASSW wc;
ZeroMemory(&wc, sizeof(wc));
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = LandlordWndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = L"LandlordWindow";
RegisterClassW(&wc);
int wtitle_len = MultiByteToWideChar(CP_UTF8, 0, title.data(), -1, NULL, 0);
wchar_t* wtitle = new wchar_t[wtitle_len];
MultiByteToWideChar(CP_UTF8, 0, title.data(), -1, wtitle, wtitle_len);
HWND hWnd = CreateWindowExW(
0, L"LandlordWindow", wtitle,
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT,
(int)width, (int)height,
NULL, NULL, GetModuleHandle(NULL), NULL
);
delete[] wtitle;
return (Int)hWnd;
}
void php_win_show_window(Int hWnd, Int cmdShow) {
ShowWindow((HWND)hWnd, (int)cmdShow);
}
Bool php_win_quit_requested() {
return g_quitRequested;
}
void php_win_post_quit(Int exitCode) {
PostQuitMessage((int)exitCode);
}
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();
}
Int php_win_get_tick_count() {
return (Int)GetTickCount();
}
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;
}
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);
HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
ReleaseDC((HWND)hWnd, hdc);
PaintFrame* frame = new PaintFrame;
frame->memDC = memDC;
frame->bitmap = memBitmap;
frame->oldBitmap = oldBitmap;
return (Int)frame;
}
void php_win_end_paint(Int hWnd, Int hdcHandle) {
PaintFrame* frame = (PaintFrame*)hdcHandle;
if (frame == NULL || frame->memDC == NULL) {
return;
}
HDC memDC = frame->memDC;
RECT rc;
GetClientRect((HWND)hWnd, &rc);
HDC hdc = GetDC((HWND)hWnd);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, memDC, 0, 0, SRCCOPY);
ReleaseDC((HWND)hWnd, hdc);
SelectObject(memDC, frame->oldBitmap);
DeleteObject(frame->bitmap);
DeleteDC(memDC);
delete frame;
}
void php_win_fill_rect(Int hdc, Int x, Int y, Int w, Int h, Int rgbColor) {
HDC targetDC = frame_hdc(hdc);
HBRUSH brush = CreateSolidBrush((COLORREF)rgbColor);
RECT r = {(int)x, (int)y, (int)(x + w), (int)(y + h)};
FillRect(targetDC, &r, brush);
DeleteObject(brush);
}
void php_win_fill_ellipse(Int hdc, Int x, Int y, Int w, Int h, Int rgbColor) {
HDC targetDC = frame_hdc(hdc);
HBRUSH brush = CreateSolidBrush((COLORREF)rgbColor);
HBRUSH oldBrush = (HBRUSH)SelectObject(targetDC, brush);
HPEN pen = CreatePen(PS_SOLID, 1, (COLORREF)rgbColor);
HPEN oldPen = (HPEN)SelectObject(targetDC, pen);
Ellipse(targetDC, (int)x, (int)y, (int)(x + w), (int)(y + h));
SelectObject(targetDC, oldPen);
SelectObject(targetDC, oldBrush);
DeleteObject(pen);
DeleteObject(brush);
}
void php_win_draw_line(Int hdc, Int x1, Int y1, Int x2, Int y2, Int rgbColor) {
HDC targetDC = frame_hdc(hdc);
HPEN pen = CreatePen(PS_SOLID, 1, (COLORREF)rgbColor);
HPEN oldPen = (HPEN)SelectObject(targetDC, pen);
MoveToEx(targetDC, (int)x1, (int)y1, NULL);
LineTo(targetDC, (int)x2, (int)y2);
SelectObject(targetDC, oldPen);
DeleteObject(pen);
}
void php_win_draw_text(Int hdc, Int x, Int y, String text, Int fontSize, Int rgbColor, Int bold) {
HDC targetDC = frame_hdc(hdc);
SetTextColor(targetDC, (COLORREF)rgbColor);
SetBkMode(targetDC, TRANSPARENT);
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);
HFONT hFont = CreateFontW((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, L"Microsoft YaHei UI");
HFONT oldFont = (HFONT)SelectObject(targetDC, hFont);
TextOutW(targetDC, (int)x, (int)y, wtext, (int)wcslen(wtext));
SelectObject(targetDC, oldFont);
DeleteObject(hFont);
delete[] wtext;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,258 @@
<?php
const SW_SHOW = 5;
const WM_KEYDOWN = 0x0100;
const WM_LBUTTONDOWN = 0x0201;
const WM_QUIT = 0x0012;
const MB_OK = 0x00000000;
const VK_N = 0x4E;
const WINDOW_WIDTH = 1180;
const WINDOW_HEIGHT = 760;
const CARD_W = 68;
const CARD_H = 96;
const CARD_GAP = 36;
const BTN_Y = 506;
const BTN_W = 138;
const BTN_H = 42;
const BTN_PLAY_X = 430;
const BTN_PASS_X = 588;
const BTN_NEW_X = 746;
function rgb(int $r, int $g, int $b): int
{
return ($r | ($g << 8) | ($b << 16));
}
function mouse_x(int $lParam): int
{
$x = $lParam & 0xFFFF;
return $x >= 0x8000 ? $x - 0x10000 : $x;
}
function mouse_y(int $lParam): int
{
$y = ($lParam >> 16) & 0xFFFF;
return $y >= 0x8000 ? $y - 0x10000 : $y;
}
function in_rect(int $x, int $y, int $rx, int $ry, int $rw, int $rh): bool
{
return $x >= $rx && $x <= $rx + $rw && $y >= $ry && $y <= $ry + $rh;
}
function card_rank(int $card): int
{
if ($card == 52) {
return 16;
}
if ($card == 53) {
return 17;
}
return intdiv($card, 4) + 3;
}
function card_suit(int $card): int
{
return $card % 4;
}
function rank_label(int $rank): string
{
$labels = [
3 => '3', 4 => '4', 5 => '5', 6 => '6', 7 => '7', 8 => '8', 9 => '9',
10 => '10', 11 => 'J', 12 => 'Q', 13 => 'K', 14 => 'A', 15 => '2',
16 => '小王', 17 => '大王',
];
return $labels[$rank] ?? '?';
}
function card_label(int $card): string
{
$rank = card_rank($card);
if ($rank >= 16) {
return 'JOKER';
}
$suits = ['黑', '红', '梅', '方'];
return rank_label($rank) . $suits[card_suit($card)];
}
function card_suit_label(int $card): string
{
$rank = card_rank($card);
if ($rank >= 16) {
return '';
}
$suits = ['♠', '♥', '♣', '♦'];
return $suits[card_suit($card)];
}
function sort_cards(array &$cards): void
{
usort($cards, function (int $a, int $b): int {
$ra = card_rank($a);
$rb = card_rank($b);
return $ra == $rb ? card_suit($a) <=> card_suit($b) : $ra <=> $rb;
});
}
function rank_counts(array $cards): array
{
$counts = [];
foreach ($cards as $card) {
$rank = card_rank($card);
$counts[$rank] = ($counts[$rank] ?? 0) + 1;
}
ksort($counts);
return $counts;
}
function is_consecutive(array $ranks): bool
{
sort($ranks);
for ($i = 1; $i < count($ranks); $i++) {
if ($ranks[$i] != $ranks[$i - 1] + 1) {
return false;
}
}
return true;
}
function analyze_play(array $cards): array
{
$n = count($cards);
if ($n == 0) {
return ['valid' => false, 'type' => 'none', 'main' => 0, 'len' => 0];
}
$counts = rank_counts($cards);
$ranks = array_keys($counts);
$values = array_values($counts);
sort($values);
if ($n == 2 && in_array(16, $ranks, true) && in_array(17, $ranks, true)) {
return ['valid' => true, 'type' => 'rocket', 'main' => 17, 'len' => 2];
}
if ($n == 4 && count($counts) == 1) {
return ['valid' => true, 'type' => 'bomb', 'main' => $ranks[0], 'len' => 4];
}
if ($n == 6 && $values == [1, 1, 4]) {
foreach ($counts as $rank => $count) {
if ($count == 4) {
return ['valid' => true, 'type' => 'four_two_single', 'main' => $rank, 'len' => 6];
}
}
}
if ($n == 8 && $values == [2, 2, 4]) {
foreach ($counts as $rank => $count) {
if ($count == 4) {
return ['valid' => true, 'type' => 'four_two_pair', 'main' => $rank, 'len' => 8];
}
}
}
if ($n == 1) {
return ['valid' => true, 'type' => 'single', 'main' => $ranks[0], 'len' => 1];
}
if ($n == 2 && count($counts) == 1) {
return ['valid' => true, 'type' => 'pair', 'main' => $ranks[0], 'len' => 2];
}
if ($n == 3 && count($counts) == 1) {
return ['valid' => true, 'type' => 'triple', 'main' => $ranks[0], 'len' => 3];
}
if ($n == 4 && $values == [1, 3]) {
foreach ($counts as $rank => $count) {
if ($count == 3) {
return ['valid' => true, 'type' => 'triple_single', 'main' => $rank, 'len' => 4];
}
}
}
if ($n == 5 && $values == [2, 3]) {
foreach ($counts as $rank => $count) {
if ($count == 3) {
return ['valid' => true, 'type' => 'triple_pair', 'main' => $rank, 'len' => 5];
}
}
}
if ($n >= 5 && count($counts) == $n && max($ranks) < 15 && is_consecutive($ranks)) {
return ['valid' => true, 'type' => 'straight', 'main' => max($ranks), 'len' => $n];
}
if ($n >= 6 && $n % 2 == 0 && min($values) == 2 && max($values) == 2 && max($ranks) < 15 && is_consecutive($ranks)) {
return ['valid' => true, 'type' => 'pair_sequence', 'main' => max($ranks), 'len' => $n];
}
if ($n >= 6 && $n % 3 == 0 && min($values) == 3 && max($values) == 3 && max($ranks) < 15 && is_consecutive($ranks)) {
return ['valid' => true, 'type' => 'airplane', 'main' => max($ranks), 'len' => $n];
}
return ['valid' => false, 'type' => 'invalid', 'main' => 0, 'len' => $n];
}
function can_beat(array $play, array $last): bool
{
if (!$play['valid']) {
return false;
}
if (!$last['valid']) {
return true;
}
if ($play['type'] == 'rocket') {
return $last['type'] != 'rocket';
}
if ($play['type'] == 'bomb' && $last['type'] != 'bomb' && $last['type'] != 'rocket') {
return true;
}
return $play['type'] == $last['type'] && $play['len'] == $last['len'] && $play['main'] > $last['main'];
}
function play_name(array $play): string
{
if (!$play['valid']) {
return '无';
}
$names = [
'single' => '单牌', 'pair' => '对子', 'triple' => '三张',
'triple_single' => '三带一', 'triple_pair' => '三带一对',
'four_two_single' => '四带二',
'four_two_pair' => '四带两对',
'straight' => '顺子', 'pair_sequence' => '连对',
'airplane' => '飞机', 'bomb' => '炸弹', 'rocket' => '王炸',
];
return ($names[$play['type']] ?? $play['type']) . ' ' . rank_label($play['main']);
}
function remove_cards(array &$hand, array $cards): void
{
foreach ($cards as $card) {
$idx = array_search($card, $hand, true);
if ($idx !== false) {
unset($hand[$idx]);
}
}
$hand = array_values($hand);
}
function hand_strength(array $hand): int
{
$score = 0;
$counts = rank_counts($hand);
foreach ($hand as $card) {
$rank = card_rank($card);
if ($rank >= 16) {
$score += 7;
} elseif ($rank == 15) {
$score += 4;
} elseif ($rank >= 13) {
$score += 2;
}
}
foreach ($counts as $rank => $count) {
if ($count == 4) {
$score += 9;
} elseif ($count == 3) {
$score += 4;
} elseif ($count == 2 && $rank >= 12) {
$score += 2;
}
}
return $score;
}

@ -0,0 +1,16 @@
<?php
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 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_fill_ellipse(int $hdc, int $x, int $y, int $w, int $h, 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 {}

@ -0,0 +1,7 @@
name: landlord
version: 1.0.0
mode: bin
sources:
- main.php
- ./php-src
- ./cpp-src

@ -2,7 +2,7 @@
<?php
/**
* Windows 打包脚本
* 将构建好的 swoole_compiler.exe 及相关文件打包为 zip
* 将构建好的 tpc.exe 及相关文件打包为 zip
* 参考 package.sh,但针对 Windows 特性进行了调整
*/
@ -39,7 +39,7 @@ if (empty($phpxDir)) {
echo "PHP 目录: {$phpDir}\n";
echo "phpx 目录: {$phpxDir}\n\n";
$compilerExe = 'swoole_compiler.exe';
$compilerExe = 'tpc.exe';
$versionFile = 'version.txt';
// ==================== 1. 版本号管理 ====================
@ -67,7 +67,7 @@ $arch = match (getenv('PROCESSOR_ARCHITECTURE')) {
};
$osType = 'windows';
$outputFile = "swoole_compiler_v{$versionId}_{$osType}_{$arch}.zip";
$outputFile = "tpc_v{$versionId}_{$osType}_{$arch}.zip";
echo "操作系统: {$osType}\n";
echo "硬件架构: {$arch}\n";
@ -146,7 +146,7 @@ echo "\n";
echo "[5/7] 准备 PHP 目录结构...\n";
// 创建顶层目录(压缩包根目录)
$topLevelDir = "swoole_compiler_v{$versionId}_{$osType}_{$arch}";
$topLevelDir = "tpc_v{$versionId}_{$osType}_{$arch}";
// 清理旧的临时目录
if (is_dir($topLevelDir)) {
@ -426,7 +426,7 @@ echo " - examples/tetris-win32/ (俄罗斯方块游戏实例)\n\n";
echo "使用说明:\n";
echo " 1. 解压到任意目录\n";
echo " 2. 确保 PHP 8.4+ 已安装并添加到 PATH\n";
echo " 3. 运行: php swoole_compiler.php <your_script.php>\n\n";
echo " 3. 运行: tpc <your_script.php>\n\n";
/**
* 递归复制目录

@ -1 +1 @@
1080
1082
Loading…
Cancel
Save