看到知乎有大神用c只要十几行代码就可以做出一个贪吃蛇,于是突然想用php也做一个。达到大神的程度很难,但好歹也能够跑起来。这里分享出来,自己是在WSL+PHP7.4下开发的,其他环境懒得测试了。
<?php
// 格子状态
const PIXEL_STATUS_SPACE=0;
const PIXEL_STATUS_UNPENATRABLE=1;
const PIXEL_STATUS_FOOD=2;
const PIXEL_STATUS_ENTER=3;
// 格子显示字符
const PIXEL_BRICK='[]';
const PIXEL_FOOD='()';
const PIXEL_SPACE=' ';
const PIXEL_ENTER=PHP_EOL;
// 墙宽和高
const SCREEN_HEIGHT=20;
const SCREEN_WIDTH=32;
/**
* 命令行光标控制
*/
class Terminal{
public static function cursorUp(int $line){
echo "\033[{$line}A";
}
public static function cursorDown(int $line){
echo "\033[{$line}B";
}
public static function write(string $string){
echo $string;
}
}
/**
* 计算发生变化的行
*
* @param array $rows
* @return array
*/
function detectChangedPixels(array $rows):array{
static $prevRows=[];
$diff=[];
foreach ($rows as $rowKey=>$pixels){
if(!isset($prevRows[$rowKey]) || $prevRows[$rowKey]!==$pixels){
$diff[$rowKey]=$pixels;
}
}
$prevRows=$rows;
return $diff;
}
/**
* 刷新渲染
*
* @param bool $init
*/
function refresh(bool $init=false):void{
global $rows;
$changedPixels=detectChangedPixels($rows);
foreach ($changedPixels as $row=>$pixels){
// 移动光标到需要刷新的行
if(!$init){
Terminal::cursorUp(SCREEN_HEIGHT-$row+2);
}
$pixels=array_map(function($pixel){
return [
PIXEL_STATUS_SPACE=>PIXEL_SPACE,
PIXEL_STATUS_UNPENATRABLE=>PIXEL_BRICK,
PIXEL_STATUS_ENTER=>PIXEL_ENTER,
PIXEL_STATUS_FOOD=>PIXEL_FOOD
][$pixel];
},$pixels);
Terminal::write(implode('',$pixels));
// 光标复位
if(!$init){
Terminal::cursorDown(SCREEN_HEIGHT-$row+2);
}
}
}
// 画墙
$rows=[];
$rows[]=array_merge(array_fill(0,SCREEN_WIDTH,PIXEL_STATUS_UNPENATRABLE),[PIXEL_STATUS_ENTER]);
for($h=0;$h<SCREEN_HEIGHT;$h++){
$_row=array_fill(0,SCREEN_WIDTH-1,PIXEL_STATUS_SPACE);
$_row[0]=PIXEL_STATUS_UNPENATRABLE;
$_row[count($_row)]=PIXEL_STATUS_UNPENATRABLE;
$_row[]=PIXEL_STATUS_ENTER;
$rows[]=$_row;
}
$rows[]=array_merge(array_fill(0,SCREEN_WIDTH,PIXEL_STATUS_UNPENATRABLE),[PIXEL_STATUS_ENTER]);
refresh(true);
// 画蛇?和食物
$snake=[[10,10],[10,9]];
$food=[13,7];
$rows[10][10]=$rows[10][9]=PIXEL_STATUS_UNPENATRABLE;
$rows[13][7]=PIXEL_STATUS_FOOD;
refresh();
// 刷新食物位置
function food(){
global $snake,$food,$rows;
$availableSpaces=[];
// 所有可用空间
for($h=1;$h<SCREEN_HEIGHT;$h++){
for ($w=1;$w<SCREEN_WIDTH;$w++){
if(in_array([$h,$w],$snake)){
continue;
}
$availableSpaces[]=[$h,$w];
}
}
if($availableSpaces){
$food=$availableSpaces[array_rand($availableSpaces)];
$rows[$food[0]][$food[1]]=PIXEL_STATUS_FOOD;
return true;
}
return false;
}
/**
* 移动蛇
*
* @param string|null $move
*/
function snake(string $move=null){
global $snake,$rows,$food;
static $lastMove='d';
$movementMap=[
'w'=>[-1,0,1],
'a'=>[0,-1,2],
's'=>[1,0,-1],
'd'=>[0,1,-2]
];
if(!is_null($move) && isset($movementMap[$move])){
if($movementMap[$lastMove][2]+$movementMap[$move][2]!==0){
$lastMove=$move;
}
}
$head=$snake[0];
$newHead=[$head[0]+$movementMap[$lastMove][0],$head[1]+$movementMap[$lastMove][1]];
// 撞墙或吃自己
if($newHead[0]===0 || $newHead[0]===SCREEN_HEIGHT+1 || $newHead[1]===0 || $newHead[1]===SCREEN_WIDTH-1 || in_array($newHead,$snake)){
exit('Game Over'.PHP_EOL);
}
array_unshift($snake,$newHead);
$rows[$newHead[0]][$newHead[1]]=PIXEL_STATUS_UNPENATRABLE;
if($newHead===$food){
//没有食物判定为赢
if(!food()){
exit('You Win'.PHP_EOL);
}
}else{
$tail=array_pop($snake);
$rows[$tail[0]][$tail[1]]=PIXEL_STATUS_SPACE;
}
}
// 非阻塞获取输入
stream_set_blocking(STDIN,false);
// 老实说,不太明白这个函数,跟获取输入有关
readline_callback_handler_install('',function(){});
while (true){
$key=stream_get_contents(STDIN,1);
if($key==='q'){
exit();
}
snake(empty($key)?null:$key);
refresh();
sleep(1);
}