#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h> #ifdef _MSC_VER // M$的编译器要给予特殊照顾
#if _MSC_VER <= 1200 // VC6及以下版本
#error 你是不是还在用VC6?!
#else // VC6以上版本
#if _MSC_VER >= 1600 // 据说VC10及以上版本有stdint.h了
#include <stdint.h>
#else // VC10以下版本,自己定义int8_t和uint16_t
typedef signed char int8_t;
typedef unsigned short uint16_t;
#endif
#ifndef __cplusplus // 据说VC都没有stdbool.h,不用C++编译,自己定义bool
typedef int bool;
#define true 1
#define false 0
#endif
#endif
#else // 其他的编译器都好说
#include <stdint.h>
#ifndef __cplusplus // 不用C++编译,需要stdbool.h里的bool
#include <stdbool.h>
#endif
#endif // =============================================================================
// 7种方块的4旋转状态(4位为一行)
static const uint16_t gs_uTetrisTable[][] = { { 0x00F0U, 0x2222U, 0x00F0U,
0x2222U }, // I型
{ 0x0072U, 0x0262U, 0x0270U, 0x0232U }, // T型
{ 0x0223U, 0x0074U, 0x0622U, 0x0170U }, // L型
{ 0x0226U, 0x0470U, 0x0322U, 0x0071U }, // J型
{ 0x0063U, 0x0264U, 0x0063U, 0x0264U }, // Z型
{ 0x006CU, 0x0462U, 0x006CU, 0x0462U }, // S型
{ 0x0660U, 0x0660U, 0x0660U, 0x0660U } // O型
}; // =============================================================================
// 初始状态的游戏池
// 每个元素表示游戏池的一行,下标大的是游戏池底部
// 两端各置2个1,底部2全置为1,便于进行碰撞检测
// 这样一来游戏池的宽度为12列
// 如果想要传统的10列,只需多填两个1即可(0xE007),当然显示相关部分也要随之改动
// 当某个元素为0xFFFFU时,说明该行已被填满
// 顶部4行用于给方块,不显示出来
// 再除去底部2行,显示出来的游戏池高度为22行
static const uint16_t gs_uInitialTetrisPool[] = { 0xC003U, 0xC003U, 0xC003U,
0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,
0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,
0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xFFFFU,
0xFFFFU }; #define COL_BEGIN 2
#define COL_END 14
#define ROW_BEGIN 4
#define ROW_END 26 // =============================================================================
typedef struct TetrisManager // 这个结构体存储游戏相关数据
{
uint16_t pool[]; // 游戏池
int8_t x; // 当前方块x坐标,此处坐标为方块左上角坐标
int8_t y; // 当前方块y坐标
int8_t type[]; // 当前、下一个和下下一个方块类型
int8_t orientation[]; // 当前、下一个和下下一个方块旋转状态
unsigned score; // 得分
unsigned erasedCount[]; // 消行数
unsigned erasedTotal; // 消行总数
unsigned tetrisCount[]; // 各方块数
unsigned tetrisTotal; // 方块总数
bool dead; // 挂
} TetrisManager; // =============================================================================
typedef struct TetrisControl // 这个结构体存储控制相关数据
{
bool pause; // 暂停
bool clockwise; // 旋转方向:顺时针为true
int8_t direction; // 移动方向:0向左移动 1向右移动
// 游戏池内每格的颜色
// 由于此版本是彩色的,仅用游戏池数据无法存储颜色信息
// 当然,如果只实现单色版的,就没必要用这个数组了
int8_t color[][];
} TetrisControl; HANDLE g_hConsoleOutput; // 控制台输出句柄 // =============================================================================
// 函数声明
// 如果使用全局变量方式实现,就没必要传参了
void initGame(TetrisManager *manager, TetrisControl *control); // 初始化游戏
void restartGame(TetrisManager *manager, TetrisControl *control); // 重新开始游戏
void giveTetris(TetrisManager *manager); // 给一个方块
bool checkCollision(const TetrisManager *manager); // 碰撞检测
void insertTetris(TetrisManager *manager); // 插入方块
void removeTetris(TetrisManager *manager); // 移除方块
void horzMoveTetris(TetrisManager *manager, TetrisControl *control); // 水平移动方块
void moveDownTetris(TetrisManager *manager, TetrisControl *control); // 向下移动方块
void rotateTetris(TetrisManager *manager, TetrisControl *control); // 旋转方块
void dropDownTetris(TetrisManager *manager, TetrisControl *control); // 方块直接落地
bool checkErasing(TetrisManager *manager, TetrisControl *control); // 消行检测
void keydownControl(TetrisManager *manager, TetrisControl *control, int key); // 键按下
void setPoolColor(const TetrisManager *manager, TetrisControl *control); // 设置颜色
void gotoxyWithFullwidth(short x, short y); // 以全角定位
void printPoolBorder(); // 显示游戏池边界
void printTetrisPool(const TetrisManager *manager, const TetrisControl *control); // 显示游戏池
void printCurrentTetris(const TetrisManager *manager,
const TetrisControl *control); // 显示当前方块
void printNextTetris(const TetrisManager *manager); // 显示下一个和下下一个方块
void printScore(const TetrisManager *manager); // 显示得分信息
void runGame(TetrisManager *manager, TetrisControl *control); // 运行游戏
void printPrompting(); // 显示提示信息
bool ifPlayAgain(); // 再来一次 // =============================================================================
// 出处:http://www.oschina.net/code/snippet_255612_16922
// 主函数
int main() {
TetrisManager tetrisManager;
TetrisControl tetrisControl; initGame(&tetrisManager, &tetrisControl); // 初始化游戏
do {
printPrompting(); // 显示提示信息
printPoolBorder(); // 显示游戏池边界
runGame(&tetrisManager, &tetrisControl); // 运行游戏
if (ifPlayAgain()) // 再来一次
{
SetConsoleTextAttribute(g_hConsoleOutput, 0x7);
system("cls"); // 清屏
restartGame(&tetrisManager, &tetrisControl); // 重新开始游戏
}
else {
break;
}
} while ();
gotoxyWithFullwidth(, );
CloseHandle(g_hConsoleOutput);
return ;
} // =============================================================================
// 初始化游戏
void initGame(TetrisManager *manager, TetrisControl *control) {
CONSOLE_CURSOR_INFO cursorInfo = { , FALSE }; // 光标信息 g_hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取控制台输出句柄
SetConsoleCursorInfo(g_hConsoleOutput, &cursorInfo); // 设置光标隐藏
SetConsoleTitleA("俄罗斯方块"); restartGame(manager, control);
} // =============================================================================
// 重新开始游戏
void restartGame(TetrisManager *manager, TetrisControl *control) {
memset(manager, , sizeof(TetrisManager)); // 全部置0 // 初始化游戏池
memcpy(manager->pool, gs_uInitialTetrisPool, sizeof(uint16_t[]));
srand((unsigned)time(NULL)); // 设置随机种子 manager->type[] = rand() % ; // 下一个
manager->orientation[] = rand() & ; manager->type[] = rand() % ; // 下下一个
manager->orientation[] = rand() & ; memset(control, , sizeof(TetrisControl)); // 全部置0 giveTetris(manager); // 给下一个方块
setPoolColor(manager, control); // 设置颜色
} // =============================================================================
// 给一个方块
void giveTetris(TetrisManager *manager) {
uint16_t tetris; manager->type[] = manager->type[]; // 下一个方块置为当前
manager->orientation[] = manager->orientation[]; manager->type[] = manager->type[]; // 下下一个置方块为下一个
manager->orientation[] = manager->orientation[]; manager->type[] = rand() % ; // 随机生成下下一个方块
manager->orientation[] = rand() & ; tetris = gs_uTetrisTable[manager->type[]][manager->orientation[]]; // 当前方块 // 设置当前方块y坐标,保证刚给出时只显示方块最下面一行
// 这种实现使得玩家可以以很快的速度将方块落在不显示出来的顶部4行内
if (tetris & 0xF000) {
manager->y = ;
}
else {
manager->y = (tetris & 0xFF00) ? : ;
}
manager->x = ; // 设置当前方块x坐标 if (checkCollision(manager)) // 检测到碰撞
{
manager->dead = true; // 标记游戏结束
}
else // 未检测到碰撞
{
insertTetris(manager); // 将当前方块加入游戏池
} ++manager->tetrisTotal; // 方块总数
++manager->tetrisCount[manager->type[]]; // 相应方块数 printNextTetris(manager); // 显示下一个方块
printScore(manager); // 显示得分信息
} // =============================================================================
// 碰撞检测
bool checkCollision(const TetrisManager *manager) {
// 当前方块
uint16_t tetris = gs_uTetrisTable[manager->type[]][manager->orientation[]];
uint16_t dest = ; // 获取当前方块在游戏池中的区域:
// 游戏池坐标x y处小方格信息,按低到高存放在16位无符号数中
dest |= (((manager->pool[manager->y + ] >> manager->x) << 0x0) & 0x000F);
dest |= (((manager->pool[manager->y + ] >> manager->x) << 0x4) & 0x00F0);
dest |= (((manager->pool[manager->y + ] >> manager->x) << 0x8) & 0x0F00);
dest |= (((manager->pool[manager->y + ] >> manager->x) << 0xC) & 0xF000); // 若当前方块与目标区域存在重叠(碰撞),则位与的结果不为0
return ((dest & tetris) != );
} // =============================================================================
// 插入方块
void insertTetris(TetrisManager *manager) {
// 当前方块
uint16_t tetris = gs_uTetrisTable[manager->type[]][manager->orientation[]]; // 当前方块每4位取出,位或到游戏池相应位置,即完成插入方块
manager->pool[manager->y + ] |= (((tetris >> 0x0) & 0x000F) << manager->x);
manager->pool[manager->y + ] |= (((tetris >> 0x4) & 0x000F) << manager->x);
manager->pool[manager->y + ] |= (((tetris >> 0x8) & 0x000F) << manager->x);
manager->pool[manager->y + ] |= (((tetris >> 0xC) & 0x000F) << manager->x);
} // =============================================================================
// 移除方块
void removeTetris(TetrisManager *manager) {
// 当前方块
uint16_t tetris = gs_uTetrisTable[manager->type[]][manager->orientation[]]; // 当前方块每4位取出,按位取反后位与到游戏池相应位置,即完成移除方块
manager->pool[manager->y + ] &=
~(((tetris >> 0x0) & 0x000F) << manager->x);
manager->pool[manager->y + ] &=
~(((tetris >> 0x4) & 0x000F) << manager->x);
manager->pool[manager->y + ] &=
~(((tetris >> 0x8) & 0x000F) << manager->x);
manager->pool[manager->y + ] &=
~(((tetris >> 0xC) & 0x000F) << manager->x);
} // =============================================================================
// 设置颜色
void setPoolColor(const TetrisManager *manager, TetrisControl *control) {
// 由于显示游戏池时,先要在游戏池里判断某一方格有方块才显示相应方格的颜色
// 这里只作设置即可,没必要清除
// 当移动方块或给一个方块时调用 int8_t i, x, y; // 当前方块
uint16_t tetris = gs_uTetrisTable[manager->type[]][manager->orientation[]]; for (i = ; i < ; ++i) {
y = (i >> ) + manager->y; // 待设置的列
if (y > ROW_END) // 超过底部限制
{
break;
}
x = (i & ) + manager->x; // 待设置的行
if ((tetris >> i) & ) // 检测的到小方格属于当前方块区域
{
control->color[y][x] = (manager->type[] | ); // 设置颜色
}
}
} // =============================================================================
// 旋转方块
void rotateTetris(TetrisManager *manager, TetrisControl *control) {
int8_t ori = manager->orientation[]; // 记录原旋转状态 removeTetris(manager); // 移走当前方块 // 顺/逆时针旋转
manager->orientation[] =
(control->clockwise) ? ((ori + ) & ) : ((ori + ) & ); if (checkCollision(manager)) // 检测到碰撞
{
manager->orientation[] = ori; // 恢复为原旋转状态
insertTetris(manager); // 放入当前方块。由于状态没改变,不需要设置颜色
}
else {
insertTetris(manager); // 放入当前方块
setPoolColor(manager, control); // 设置颜色
printCurrentTetris(manager, control); // 显示当前方块
}
} // =============================================================================
// 水平移动方块
void horzMoveTetris(TetrisManager *manager, TetrisControl *control) {
int x = manager->x; // 记录原列位置 removeTetris(manager); // 移走当前方块
control->direction == ? (--manager->x) : (++manager->x); // 左/右移动 if (checkCollision(manager)) // 检测到碰撞
{
manager->x = x; // 恢复为原列位置
insertTetris(manager); // 放入当前方块。由于位置没改变,不需要设置颜色
}
else {
insertTetris(manager); // 放入当前方块
setPoolColor(manager, control); // 设置颜色
printCurrentTetris(manager, control); // 显示当前方块
}
} // =============================================================================
// 向下移动方块
void moveDownTetris(TetrisManager *manager, TetrisControl *control) {
int8_t y = manager->y; // 记录原行位置 removeTetris(manager); // 移走当前方块
++manager->y; // 向下移动 if (checkCollision(manager)) // 检测到碰撞
{
manager->y = y; // 恢复为原行位置
insertTetris(manager); // 放入当前方块。由于位置没改变,不需要设置颜色
if (checkErasing(manager, control)) // 检测到消行
{
printTetrisPool(manager, control); // 显示游戏池
}
}
else {
insertTetris(manager); // 放入当前方块
setPoolColor(manager, control); // 设置颜色
printCurrentTetris(manager, control); // 显示当前方块
}
} // =============================================================================
// 方块直接落地
void dropDownTetris(TetrisManager *manager, TetrisControl *control) {
removeTetris(manager); // 移走当前方块
for (; manager->y < ROW_END; ++manager->y) // 从上往下
{
if (checkCollision(manager)) // 检测到碰撞
{
break;
}
}
--manager->y; // 上移一格当然没有碰撞 insertTetris(manager); // 放入当前方块
setPoolColor(manager, control); // 设置颜色 checkErasing(manager, control); // 检测消行
printTetrisPool(manager, control); // 显示游戏池
} // =============================================================================
// 消行检测
bool checkErasing(TetrisManager *manager, TetrisControl *control) {
static const unsigned scores[] = { , , , , }; // 消行得分
int8_t count = ;
int8_t k = , y = manager->y + ; // 从下往上检测 do {
if (y < ROW_END && manager->pool[y] == 0xFFFFU) // 有效区域内且一行已填满
{
++count;
// 消除一行方块
memmove(manager->pool + , manager->pool, sizeof(uint16_t) * y);
// 颜色数组的元素随之移动
memmove(control->color[], control->color[],
sizeof(int8_t[]) * y);
}
else {
--y;
++k;
}
} while (y >= manager->y && k < ); manager->erasedTotal += count; // 消行总数
manager->score += scores[count]; // 得分 if (count > ) {
++manager->erasedCount[count - ]; // 消行
} giveTetris(manager); // 给下一个方块
setPoolColor(manager, control); // 设置颜色 return (count > );
} // =============================================================================
// 键按下
void keydownControl(TetrisManager *manager, TetrisControl *control, int key) {
if (key == ) // 暂停/解除暂停
{
control->pause = !control->pause;
} if (control->pause) // 暂停状态,不作处理
{
return;
} switch (key) {
case 'w':
case 'W':
case '':
case : // 上
control->clockwise = true; // 顺时针旋转
rotateTetris(manager, control); // 旋转方块
break;
case 'a':
case 'A':
case '':
case : // 左
control->direction = ; // 向左移动
horzMoveTetris(manager, control); // 水平移动方块
break;
case 'd':
case 'D':
case '':
case : // 右
control->direction = ; // 向右移动
horzMoveTetris(manager, control); // 水平移动方块
break;
case 's':
case 'S':
case '':
case : // 下
moveDownTetris(manager, control); // 向下移动方块
break;
case ' ': // 直接落地
dropDownTetris(manager, control);
break;
case '': // 反转
control->clockwise = false; // 逆时针旋转
rotateTetris(manager, control); // 旋转方块
break;
default:
break;
}
} // =============================================================================
// 以全角定位
void gotoxyWithFullwidth(short x, short y) {
static COORD cd; cd.X = (short)(x << );
cd.Y = y;
SetConsoleCursorPosition(g_hConsoleOutput, cd);
} // =============================================================================
// 显示游戏池边界
void printPoolBorder() {
int8_t y; SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);
for (y = ROW_BEGIN; y < ROW_END; ++y) // 不显示顶部4行和底部2行
{
gotoxyWithFullwidth(, y - );
printf("%2s", "");
gotoxyWithFullwidth(, y - );
printf("%2s", "");
} gotoxyWithFullwidth(, y - ); // 底部边界
printf("%28s", "");
} // 定位到游戏池中的方格
#define gotoxyInPool(x, y) gotoxyWithFullwidth(x + 9, y - 3) // =============================================================================
// 显示游戏池
void printTetrisPool(const TetrisManager *manager, const TetrisControl *control) {
int8_t x, y; for (y = ROW_BEGIN; y < ROW_END; ++y) // 不显示顶部4行和底部2行
{
gotoxyInPool(, y);
// 定点到游戏池中的方格
for (x = COL_BEGIN; x < COL_END; ++x) // 不显示左右边界
{
if ((manager->pool[y] >> x) & ) // 游戏池该方格有方块
{
// 用相应颜色,显示一个实心方块
SetConsoleTextAttribute(g_hConsoleOutput, control->color[y][x]);
printf("■");
}
else // 没有方块,显示空白
{
SetConsoleTextAttribute(g_hConsoleOutput, );
printf("%2s", "");
}
}
}
} // =============================================================================
// 显示当前方块
void printCurrentTetris(const TetrisManager *manager,
const TetrisControl *control) {
int8_t x, y; // 显示当前方块是在移动后调用的,为擦去移动前的方块,需要扩展显示区域
// 由于不可能向上移动,故不需要向下扩展
y = (manager->y > ROW_BEGIN) ? (manager->y - ) : ROW_BEGIN; // 向上扩展一格
for (; y < ROW_END && y < manager->y + ; ++y) {
x = (manager->x > COL_BEGIN) ? (manager->x - ) : COL_BEGIN; // 向左扩展一格
for (; x < COL_END && x < manager->x + ; ++x) // 向右扩展一格
{
gotoxyInPool(x, y);
// 定点到游戏池中的方格
if ((manager->pool[y] >> x) & ) // 游戏池该方格有方块
{
// 用相应颜色,显示一个实心方块
SetConsoleTextAttribute(g_hConsoleOutput, control->color[y][x]);
printf("■");
}
else // 没有方块,显示空白
{
SetConsoleTextAttribute(g_hConsoleOutput, );
printf("%2s", "");
}
}
}
} // =============================================================================
// 显示下一个和下下一个方块
void printNextTetris(const TetrisManager *manager) {
int8_t i;
uint16_t tetris; // 边框
SetConsoleTextAttribute(g_hConsoleOutput, 0xF);
gotoxyWithFullwidth(, );
printf("┏━━━━┳━━━━┓");
gotoxyWithFullwidth(, );
printf("┃%8s┃%8s┃", "", "");
gotoxyWithFullwidth(, );
printf("┃%8s┃%8s┃", "", "");
gotoxyWithFullwidth(, );
printf("┃%8s┃%8s┃", "", "");
gotoxyWithFullwidth(, );
printf("┃%8s┃%8s┃", "", "");
gotoxyWithFullwidth(, );
printf("┗━━━━┻━━━━┛"); // 下一个,用相应颜色显示
tetris = gs_uTetrisTable[manager->type[]][manager->orientation[]];
SetConsoleTextAttribute(g_hConsoleOutput, manager->type[] | );
for (i = ; i < ; ++i) {
gotoxyWithFullwidth((i & ) + , (i >> ) + );
((tetris >> i) & ) ? printf("■") : printf("%2s", "");
} // 下下一个,不显示彩色
tetris = gs_uTetrisTable[manager->type[]][manager->orientation[]];
SetConsoleTextAttribute(g_hConsoleOutput, );
for (i = ; i < ; ++i) {
gotoxyWithFullwidth((i & ) + , (i >> ) + );
((tetris >> i) & ) ? printf("■") : printf("%2s", "");
}
} // =============================================================================
// 显示得分信息
void printScore(const TetrisManager *manager) {
static const char *tetrisName = "ITLJZSO";
int8_t i; SetConsoleTextAttribute(g_hConsoleOutput, 0xE); gotoxyWithFullwidth(, );
printf("■得分:%u", manager->score); gotoxyWithFullwidth(, );
printf("■消行总数:%u", manager->erasedTotal);
for (i = ; i < ; ++i) {
gotoxyWithFullwidth(, + i);
printf("□消%d:%u", i + , manager->erasedCount[i]);
} gotoxyWithFullwidth(, );
printf("■方块总数:%u", manager->tetrisTotal); for (i = ; i < ; ++i) {
gotoxyWithFullwidth(, + i);
printf("□%c形:%u", tetrisName[i], manager->tetrisCount[i]);
}
} // =============================================================================
// 显示提示信息
void printPrompting() {
SetConsoleTextAttribute(g_hConsoleOutput, 0xB);
gotoxyWithFullwidth(, );
printf("■控制:");
gotoxyWithFullwidth(, );
printf("□向左移动:← A 4");
gotoxyWithFullwidth(, );
printf("□向右移动:→ D 6");
gotoxyWithFullwidth(, );
printf("□向下移动:↓ S 2");
gotoxyWithFullwidth(, );
printf("□顺时针转:↑ W 8");
gotoxyWithFullwidth(, );
printf("□逆时针转:0");
gotoxyWithFullwidth(, );
printf("□直接落地:空格");
gotoxyWithFullwidth(, );
printf("□暂停游戏:回车");
gotoxyWithFullwidth(, );
printf("■By: wohaaitinciu 12.12.29");
} // =============================================================================
// 运行游戏
void runGame(TetrisManager *manager, TetrisControl *control) {
clock_t clockLast, clockNow; clockLast = clock(); // 计时
printTetrisPool(manager, control); // 显示游戏池 while (!manager->dead) // 没挂
{
while (_kbhit()) // 有键按下
{
keydownControl(manager, control, _getch()); // 处理按键
} if (!control->pause) // 未暂停
{
clockNow = clock(); // 计时
// 两次记时的间隔超过0.45秒
if (clockNow - clockLast > 0.45F * CLOCKS_PER_SEC) {
clockLast = clockNow;
keydownControl(manager, control, ); // 方块往下移
}
}
}
} // =============================================================================
// 再来一次
bool ifPlayAgain() {
int ch; SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);
gotoxyWithFullwidth(, );
printf("游戏结束");
gotoxyWithFullwidth(, );
printf("按Y重玩,按N退出"); do {
ch = _getch();
if (ch == 'Y' || ch == 'y') {
return true;
}
else if (ch == 'N' || ch == 'n') {
return false;
}
} while ();
}

最新文章

  1. nodejs 使用fs实现多级联动
  2. js构建ui的统一异常处理方案(二)
  3. iOS 杂笔-23(区分各种空值)
  4. [IT新应用]如何用好搜索引擎学习英语
  5. 转: Github访问慢解决办法
  6. Java堆
  7. nova分析(4)—— nova-cells
  8. ListView 实现分组
  9. 老李分享:网页爬虫java实现
  10. 从MVC到Ajax再到前后端分离的思考
  11. 使用Github Composer Packagist编写及发布扩展包
  12. innerHTML innerText与outerHTML间的区别
  13. 为什么web3 1.0 的接口有personal_*和eth_*的,两者有什么不同
  14. CXF - JAX-WS入门
  15. Python3 Tkinter-Pack
  16. Google ProtocolBuffer
  17. jQuery UI 自定义样式的日历控件
  18. Tile Cut~网络流入门题
  19. Webpack机制、原理简单小结
  20. Spotlight安装

热门文章

  1. hdu6315 /// 线段树区间更新
  2. VUE 中 使用 iview Form组件 enter键防止页面刷新
  3. 微信小程序picker下拉绑定数据
  4. 关于第一次将STM32与电脑连接情况
  5. SQL索引操作
  6. set -x 调试shell
  7. thrift 的一些相关知识
  8. 用python输出1-100之间所有的质数
  9. BIO、NIO和AIO
  10. Burnside引理&amp;P&#243;lya定理