PHP: нормализация номера телефона

Задача: на входе строка, которая может содержать номер, или номер + какие то комментарии к нему. Необходимо нормализовать номер, что бы были только цифры.

Решение:

Сначала напишем функцию проверки наличия букв в строке:

function LeetterYet($mobile){
    $yet=false;            
        if (preg_match('/[а-яё]/iu',    $mobile)) {$yet=true;};    // символ ё, отдельно проверяем
        if (preg_match('/[a-zA-Z]/',    $mobile)) {$yet=true;};    // английский алфавит
        if (preg_match('/[^a-zA-Z\d]/',    $mobile)) {$yet=true;}; // специальные символы
    return $yet;
}

Далее, если есть буквы, то удалим их, и ограничим длинну поля 11 символами..

function NormalizeMobile($mobile){
  $mobile=preg_replace('/[^0-9]/', '', $mobile);
  $mobile = substr($mobile, 0,11);
  return $mobile;
};

И общий результат:

        $mobile=$res->result->mobile;            
        if (LeetterYet($mobile)==false){                
          $mobile=  NormalizeMobile($mobile);
        };

Пишем Тетрис на Flutter

Дело было вечером, делать было нечего (с)..В качестве разминки решил в кои-то веки написать игру. Пришла на ум идея написать давнюю мечту — тетрис. В т.ч. использовать будем более ранние наработки по работе с Hive и Requets оформленные в виде отдельных классов.

Из интересного:

1) Для отображения стакана и фигур используем три массива:

LandMatrix — массив вида ширина/высота — фактически «стакан» с «приземленными» фигурами. Точка пересечения массива — цвет квадратика. Если пусто — значит в точке стакана ничего нет

MoveFig — массив движущейся по стакану фигуры. В момент совпадения низа фигуры с любой не пустой точкой стакана фигура переносится в «стакан»

ShadowRotareFig — вспомогательный массив, используемый для поворота фигуры. В момент когда фигуру необходимо повернуть, она переносится из MoveFig в этот массив, поворачивается, и переносится обратно в MoveFig. Создание отдельного массива для поворота было обусловлено способом хранения фигур — хранится не матрица, а просто массив текущих координат квадратиков из которых составлена фигура.

2) Отрисовка стакана происходит после того, как фигура перенесена в массив стакана. Причины переноса — касание дна стакана.

List <Widget> MyLandView(){
    int poz=0;
    return List<Widget>.generate(lh, (int index_h){
      return Row(
        children:
        List<Widget>.generate(lw, (int index_w){
          poz++;
          Color BoxColor=FigColors[99]??Colors.black;
          if (MovedFig.contains(poz)==true){
            BoxColor=FigColors[fcurrent]??Colors.black;
          };
          Coords crd=Poz2Coors(poz);
          // если в стакане что-то есть - рисуем
          if (LandMatrix[crd.y][crd.x]>0){  // если  клетка не пустая
            BoxColor=FigColors[100]??Colors.black;
          };
          if (LandMatrix[crd.y][crd.x]==101){  //
            BoxColor=FigColors[101]??Colors.black;
          };
          if (BoxColor==FigColors[100]){
            //определяем это верхний блок или под ним еще что есть?
            bool ground_top=false;
              Coords crd_tmp=Poz2Coors(poz-lw);
              print("Строчка: ${crd.y},${crd.x}");
              print("Строчка вниз: ${crd_tmp.y},${crd_tmp.x}");
              if (crd_tmp.y>0) {
                if (LandMatrix[crd_tmp.y][crd_tmp.x] >0) {
                  print("-- ниже  есть земля!");
                  ground_top = true;
                }
              }
            return
                Container(
                  color: BoxColor,
                  height: MediaQuery.of(context).size.height/lh,
                  width: MediaQuery.of(context).size.width/lw,
                  child:
                    Image.asset(
                        !ground_top?"lib/images/ground.png":"lib/images/ground_full.png",
                      height: 10,
                      width: 10,
                      fit: BoxFit.fill,
                    ),
                );
          } else {
            return
              Padding(
                  padding: EdgeInsets.all(1),
                  child:
                  Container(
                    color: BoxColor,
                    height: MediaQuery.of(context).size.height / lh - 2,
                    width: MediaQuery.of(context).size.width / lw - 2,
                    //child: Text("$poz")
                  )
              );
          };
        }),
      );
    });
  }

3) При старте игры назначаем периодический таймер, при выполнении которого фигура движется на линию ниже. При движении выполняется проверка касания дна в стакане.

  void StartTimer(context){
    timer = Timer.periodic(Duration(seconds: 1), (Timer _) {
      DestroyFullLines();
      print("Сработал таймер..");
      MoveFigDown(context);
    });
  }
  // двигаем фигуру вниз
  void MoveFigDown(context){
      if (GameOver==true) {return;};
      bool move=true;
      // проверим,при следующем шаге, ни одна ли из точек не достигнет дна колодца при движении вниз?
      for (var i = 0; i < MovedFig.length; i++) {
        // проверка дна колодца
        if ((MovedFig[i] + lw)>lw*lh){
          move=false;
        };
        // проверка что наезд на другую фигуру
        Coords crd=Poz2Coors(MovedFig[i] + lw);
        if (MovedFig[i] + lw<=lw*lh) {
          if (LandMatrix[crd.y][crd.x] > 0) {
            move = false;
          };
        };
      };
      if (move==true) {
        for (var i = 0; i < MovedFig.length; i++) {
          MovedFig[i] = MovedFig[i] + lw;
        };
      };
      // если не можем двигаться вниз, то переносим фигуру в "стакан"
      if (move==false){
        print("-переносим фигуру в стакан");
        for (var i = 0; i < MovedFig.length; i++) {
          Coords crd=Poz2Coors(MovedFig[i]);
          LandMatrix[crd.y][crd.x]=1;
        };
        DestroyFullLines();
        // создаём новую фигуру
        FigureSelections();
      };
      setState(() {
        MovedFig=MovedFig;
      });
    print("Фигура двинулась вниз: ${MovedFig}");
      String audioasset = "lib/sounds/click.mp3";
      AssetsAudioPlayer.newPlayer().open(
          Audio(audioasset)
      );
  }

4) Поворот фигуры, как уже писал выше осуществляется переносом фигуры в матрицу, поворот матрицы и обратный перенос фигуры

  // поворачиваем блок
  void RotateFig(context){
    print("До поворота: ${MovedFig}");
    List <int> OldFig=List<int>.from(MovedFig); //запоминаем фигуру и положение до поворота

    // найдем минимальные координаты x и y до поворота
    Coords crd_before_rotare=GetStartXYFigure(MovedFig);
    print("min_x=${crd_before_rotare.x},min_y=${crd_before_rotare.y}");

    // передвинем фигуру в центр
    int center_x=4;
    int center_y=4;

    MovedFig=FigureMove(center_x,center_y,MovedFig);

    for (var i = 0; i < MovedFig.length; i++) {
      Coords crd=Poz2Coors(MovedFig[i]);
      int new_x=(crd.x * cos(3.1415926535897932/2) - crd.y * sin(3.1415926535897932/2)).round();
      int new_y=(crd.x * sin(3.1415926535897932/2) + crd.y * cos(3.1415926535897932/2)).round();
      MovedFig[i]=Coors2Poz(new_x,new_y);
      //print("x=${new_x},y=${new_y}");
    };
    MovedFig=FigureMove(crd_before_rotare.x-2,crd_before_rotare.y,MovedFig);

    print("После поворота: ${MovedFig}");
    // проверяем, а можно ли было поворачивать?
    if (MovedFig[0]==-1){
      MovedFig=OldFig;
    }
    // проверяем, а нет ли наложения элементов на то что уже в стакане?
    for (var i = 0; i < MovedFig.length; i++) {
      Coords crd=Poz2Coors(MovedFig[i] + lw);
      if (MovedFig[i] + lw<=lw*lh) {
        if (LandMatrix[crd.y][crd.x] > 0) {
          MovedFig=OldFig;
        };
      };
    };
  }

Исходный код игры можно скачать тут

УАЗ Патриот чудо инженерной мысли в РФ.

Итак, весной этого года окончательно пришло время менять старый автомобиль на новый. Хенде Туксан прослужил верой и правдой почти 18 лет и начал банально уже сыпаться. Стойки сгнили. Подвеска уже стала настолько изношена, что сход-развал уже даже не смогли сделать, чтоб резину не жевало. Фары потускнели. Коробка стала «пинаться». Появилась уже видимая ржавчина. Даже пластмасса в салоне настолько устала, что стала ломаться — подлокотник у водителя сломался пополам. В общем было принято решение избавиться от него и купить новый автомобиль. Продать кстати удалось на удивление быстро и относительно дорого. Человеку не лень было приехать аж из Барнаула за машиной.

Покупка б/у автомобиля не рассматривалась в принципе, ввиду крайне негативного опыта покупок в прошлом (2005г): купил ВАЗ 2115 перевертыш (ну ктож знал!), и год пришлось на нём откататься пока не нашел другого простака, который её у меня купил дороже чем я её купил. Да знакомый один тоже, лет 5 назад купил вроде с виду приличную мазду, которая тоже оказалась бита-крашена в хлам. В общем в такую лотерею играть я не захотел.

Стали рассматривать новые автомобили. Шок. Отрицание. Торг. Все стадии прошел достаточно быстро. 2 миллиона и выше отдавать за кросовер — внедорожник, влезая в кабалу кредитов и отдавая пол зарплаты — был не готов. Семья, дети, жена которая хочет шубу и в отпуск ну и всё такое. Паркетники тоже отпали — поездив на кросовере с заявкой на «внедорожник», перелезать в паркетник уже было неохота. Привык что ни зимой ни летом не парюсь и еду там где паркетники буксуют или не едут.

Стал смотреть отечественные «внедорожники» — нива шевроле и Патриот. Нива отпала по причине того что она для нашей семьи маленькая и маломощная. Да и в самой «заряженой» комплектации из тех которые были в наличии — нет даже задних стеклоподьемников и (барабанная дробь) кондиционера!

Ну что делать, остался только УАЗ Патриот. Приехали в автосалон, потрогали, полазали самую базовую модель. Вроде понравилась и внешне и размерами. Решили брать. Купили.

Итак, спустя месяц владения, могу уже описать минусы данного автомобиля и сколько я в него вложил. Плюсов пока особо не увидел кроме высокой посадки.

Вложения:

  • мультимедиа система на андроиде. Куплена магнитола, куплены 4 громкоговорителя (в базе не было — только провода). Подключить адекватно получилось со второго раза: установщики сказали что «перепутаны были провода, еле разобрались» в торпеде. Не могу проверить, так оно на самом деле или установщики безрукие.
  • Не было подголовников на задних сидениях — наколхозил сам, купив б/у диван, выдрав оттуда крепления и подголовники
  • поставил кнопку запуска. Работает не очень — иногда не гаснет иммобилайзер. Установщики говорят «нужно катушку ставить из замка». Ну будем еще разбираться чуть попозжа.
  • Задние брызговики. Почему на заводе не ставят такую просто тупо необходимую вещь совершенно не понятно. Поставили — один дребезжит при езде — нужно перделывать (мало млять других дребезжаний!)
  • Антикорозийная обработка + шумоизоляция дверей.
  • Сигнализация с автозапуском

Что уже сломалось:

  • вывалился плафон освещения в кабине. Починил сам, отогнув усики и вставив обратно
  • Что-то особо сильно дребезжит (помимо всего остального) под капотом при оборотах около 2500-3000. Нужно будет на ТО-0 искать что

Минусы пока из того что заметил:

  • Стыки сварки на кузове — выполнены очень не аккуратно.
  • Пороги прилеплены с одной стороны вроде боле мене, с другой со щелью, нужно переделывать
  • Провода под капотом кой-где висят. Пока примотал кой где изолентой и стяжками
  • Некоторые трубки под капотом соприкасаются с железом и дребезжат при езде — обернул изоляцией от труб ПВХ
  • Машина ОЧЕНЬ шумная
  • Супруга пока не научилась ездить.

Планы на будущее:

  • купить зимнюю резину и диски
  • купить и поставить рейлинги
  • переделать рычаг КПП — сделать его «безшумным» по технологиям с ютуба
  • купить пластмассовый колпак запаски
  • Пройти ТО-0, всё протянуть, попытаться убрать большинство дребезжаний (особенно тот который на оборотах 2500-3000)

Pixi.js : обработка столкновений

Вот и вылез огромный минус работы с этим 2D движком: в «базе» нет функционала по определению столкновений двух объектов. Можно конечно самому дописать этот функционал если нужно..НО..вот моя реализация распознавания столкновения двух кружков получилась совсем не оптимальной по быстродействию.

Первым делом напишем алгоритм столкновения двух окружностей. А именно вспомним школу: Две окружности пересекаются,если расстояние между центрами меньше либо равно сумме радиусов

 function CircleHit(c0,c1){
        res=false;
         r0=c0.getBounds().height/2;
         r1=c1.getBounds().height/2;
         x0=c0.getBounds().x+r0;
         x1=c1.getBounds().x+r1;
         y0=c0.getBounds().y+r0;
         y1=c1.getBounds().y+r1;                 
         len= Math.sqrt( Math.pow(x0-x1,2) + Math.pow(y0-y1,2) ); //расстояние между центрами
         sum=Math.abs(r0+r1);   //сумма радиусов
         if (len<=sum) return true;         
        return res;
    }

Далее нарисуем на холсте случайным образом кружочки. Проследим, чтоб во время разрисовки они не накладывались и не пересекались:

        for (let i = 0; i < count_grand_mamas; i++) { 
           graphics = new PIXI.Graphics();           
           graphics.beginFill(0xDE3249);
           graphics.lineStyle(2, 0xFEEB77, 1);
           graphics.beginFill(0x650A5A, 1);
           radius=randomIntFromInterval(5,30);
           graphics.drawCircle(0,0, radius);       
           need_random=true;           
           while (need_random==true){               
            x=randomIntFromInterval(1,width-2*radius);
            y=randomIntFromInterval(1,height-2*2*radius);
            if (i>0){
                need_random=false; 
                for (let it = 0; it < i-1; it++) { 
                   if (FutureCircleHit(grand_mamas[it],x,y,radius)==true) {
                      need_random=true; 
                   };
                }
            } else {
                need_random=false;
            }
           };
           graphics.position.set(x, y);          
           graphics.stepx=GetRandomStep(); // определяем направление движения по оси х          
           graphics.stepy=GetRandomStep(); // определяем направление движения по оси y
           graphics.endFill();        
           grand_mamas.push(graphics); 
           app.stage.addChild(grand_mamas[i]);            
        }  

И теперь каждый «тик» двигаем кружок, проверяя не сталкиваются ли кружки. Если сталкиваются — то меняем направление движения обоих:

        ticker.add(function (time) {     
            FPSText.text='FPS:'+ticker.FPS; // выводим FPS
            for (let i = 0; i < grand_mamas.length; i++) {                                 
                grand_mamas[i].position.x=grand_mamas[i].position.x+grand_mamas[i].stepx;
                grand_mamas[i].position.y=grand_mamas[i].position.y+grand_mamas[i].stepy;
                
                if (((grand_mamas[i].getBounds().x+grand_mamas[i].getBounds().height)>width)||(grand_mamas[i].getBounds().x<0)){
                  grand_mamas[i].stepx=-grand_mamas[i].stepx;  
                };
                if (((grand_mamas[i].getBounds().y+grand_mamas[i].getBounds().height)>height)||(grand_mamas[i].getBounds().y<0)){
                  grand_mamas[i].stepy=-grand_mamas[i].stepy;  
                };
                grand_mamas[i].hit=false;
            };                            
                // обрабатываем столкновения
                for (let i1 = 0; i1 < grand_mamas.length; i1++) { 
                    for (let i2 = 0; i2 < grand_mamas.length; i2++) { 
                        if (i1!=i2){
                            if (CircleHit(grand_mamas[i1],grand_mamas[i2])==true){                                 
                                if (grand_mamas[i1].hit==false) {   
                                    grand_mamas[i1].stepx=-1*grand_mamas[i1].stepx;  
                                    grand_mamas[i2].stepx=-1*grand_mamas[i2].stepx;  
                                    grand_mamas[i1].stepy=-1*grand_mamas[i1].stepy;  
                                    grand_mamas[i2].stepy=-1*grand_mamas[i2].stepy;                              
                                    
                                    grand_mamas[i1].hit=true;
                                    grand_mamas[i2].hit=true;
                                };
                            };
                        };
                    }
                }
                

        });

Pixi.js : первые шаги

Pixi.js — это движёк 2D графики на JavaScript. Позволяет использовать WebGL или Canvas если первый не доступен.

Ну что, попробуем его в действии. Нарисуем 2000 кружков и попробуем их подвигать.

Подключим движёк через CDN:

<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.x/dist/pixi.min.js"></script>

Обьявим массив кружков, определим размер экрана:

    grand_mamas=[];
    count_grand_mamas=2000;
    
    var width = window.innerWidth; //получаем ширину экрана
    var height = window.innerHeight; // получаем высоту экрана

Инициируем экран приложения:

    const app = new PIXI.Application({ 
        width:width,
        height:height,
        background: '#1099bb' ,
        antialias: true
    });
    document.body.appendChild(app.view);

Нарисуем 2000 кружков и поместим эти обьекты в массив, чтобы дальше с ними играться:

    for (let i = 0; i < count_grand_mamas; i++) { 
        graphics = new PIXI.Graphics();           
        graphics.beginFill(0xDE3249);
        graphics.lineStyle(2, 0xFEEB77, 1);
        graphics.beginFill(0x650A5A, 1);
        graphics.drawCircle(0,0, randomIntFromInterval(1,10));       
        graphics.position.set(randomIntFromInterval(-width,width), randomIntFromInterval(1,height));
        graphics.endFill();        
        grand_mamas.push(graphics);
        app.stage.addChild(grand_mamas[i]);
        
    };

Далее используем класс ticker для периодических действий с кружками. По умолчанию класс эвент класса выполняется 60 раз в секунду. Что делаем в эвенте? Пробегаемся по массиву кружков и двигаем их. Если кружок выходит за пределы экрана, то передвигаем его случайным образом вверх экрана. Бонусом выведем FPS:

  FPSText = new PIXI.Text('FPS:0');
  FPSText.x = 10;
  FPSText.y = 10;   
  app.stage.addChild(FPSText);
   
  let ticker = PIXI.Ticker.shared;   
  ticker.autoStart = false;
  ticker.start();
  
   step=0.01;
   ticker.add(function (time) {
        FPSText.text='FPS:'+ticker.FPS;
        for (let i = 0; i < count_grand_mamas; i++) {                 
            count=count+step;
            if (count>10){step=-step;};        
            if (count<0){step=-step;};        
            grand_mamas[i].position.x=grand_mamas[i].position.x+count;
            grand_mamas[i].position.y=grand_mamas[i].position.y+count;
            //console.log(grand_mamas[i].getBounds());
            if ((grand_mamas[i].position.x>width)||(grand_mamas[i].position.y>height)){
                grand_mamas[i].position.x=randomIntFromInterval(-width,width);
                grand_mamas[i].position.y=randomIntFromInterval(1,1);
            };
        }  
   });

В результате получим движущиеся кружочки с разной скоростью

1 44 45 46 47 48 310