php cli版贪吃蛇

/ 0评 / 0

看到知乎有大神用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);
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注