Разрабатываем для Windows Phone под Windows Phone (на примере тетриса)

в 11:09, , рубрики: microsoft, TouchDevelop, windows phone, Программирование, разработка, разработка под windows phone, метки: , ,

Однажды, возвращаясь из командировки, мне необходимо было как-то скрасить своё время. Гостиничный Wi-Fi само собой был в недосягаемости, а телефон в роуминге. Так Я познакомился с TouchDevelop, который давно был установлен на телефон, но в котором не было времени основательно покопаться. Несколько часов пролетели под написанием калькулятора, умеющего конвертировать величины.

Разрабатываем для Windows Phone под Windows Phone (на примере тетриса) Что такое TouchDevelop

Некоторую ясность может внести следущее видео:

Но если быть чуть чётче, то TouchDevelop — это приложение для Windows Phone от Microsoft Research. Оно позволяет писать скрипты, при этом основной упор сделан на удобное взаимодействие с разработчиком. В TouchDevelop присутствует ООП, есть стандартная библиотека классов для взаимодействия с телефоном (ввод/вывод данных, проигрывание музыки, видео, работа с фотокамерой, акселерометром, работа с графикой и т.д.), а также своё коммьюнити с облаком, в котором мы делимся своими результатами, смотрим что делают другие и учимся у них и которое мы можем использовать для реализации рейтинга игроков (в случае, если мы делаем игрушку) и всё это opensource (да, форки тоже можно делать). Если вы заинтересовались, то прошу под кат, где будет разобран пример разработки тетриса.

Разрабатываем для Windows Phone под Windows Phone (на примере тетриса) Пишем скрипт

Те, у кого нет Windows Phone, могут приблизительно понять как происходит разработка по следующему видео:

Для тех же у кого Windows Phone есть, нужно учесть, что статья приобретает некий интерактивный характер (т.к. на невзломанном телефоне нельзя делать скриншоты). Итак, для начала необходимо скачать приложение на Маркетплейсе: скачать TouchDevelop. После этого необходимо залогиниться и нажать плюсик внизу и дать название своему скрипту. TouchDevelop позволяет использовать классы, события, глобальные переменные, ресурсы и библиотеки (ссылки на другие скрипты). Сам тетрис будет выглядеть классическим образом:
Разрабатываем для Windows Phone под Windows Phone (на примере тетриса)Разрабатываем для Windows Phone под Windows Phone (на примере тетриса)Разрабатываем для Windows Phone под Windows Phone (на примере тетриса)
Итак, для начала определим главную функцию. Нам нужно будет определить формы фигур, поле, на котором мы играем, начальную инициализацию игры и отрисовку самих фигур, для этого в методе main напишем:

action main() {
  code→readShapeDefiniton;
  code→initGame;
  code→drawLines;
  code→drawShape();
}

Сами фигуры мы опишем через JSON:

action readShapeDefiniton() {
  $s := "{"Shapes": [n{"Name": "I-Shape",n"Tiles": [n{"x":0, "y":0},n{"x":0, "y":-1},n{"x":0, "y":1},n{"x":0, "y":2}]n},{"Name": "L-Shape",n"Tiles": [n{"x":0, "y":0},n{"x":0, "y":-1},n{"x":0, "y":1},n{"x":1, "y":1}]n},{"Name": "Z-Shape",n"Tiles": [n{"x":0, "y":0},n{"x":-1, "y":0},n{"x":0, "y":1},n{"x":1, "y":1}]n},n{"Name": "T-Shape",n"Tiles": [n{"x":0, "y":0},n{"x":-1, "y":0},n{"x":1, "y":0},n{"x":0, "y":1}]},n{"Name": "J-Shape",n"Tiles": [n{"x":0, "y":0},n{"x":0, "y":-1},n{"x":0, "y":1},n{"x":-1, "y":1}]},n{"Name": "S-Shape",n"Tiles": [n{"x":0, "y":0},n{"x":1, "y":0},n{"x":0, "y":1},n{"x":-1, "y":1}]},n{"Name": "O-Shape",n"Tiles": [n{"x":-1, "y":0},n{"x":0, "y":0},n{"x":0, "y":1},n{"x":-1, "y":1}]}n]}";
  data→ShapeDefinition := web→json($s);
  meta private;
}

Инициализация будет выглядеть следующим образом:

action initGame() {
  data→GameOver := false;
  data→BoardHeight := 768;
  data→TileSize := 34;
  data→GameBoardHeight := 20 * data→TileSize;
  data→GameBoardWidth := data→TileSize * 10;
  data→GameBoardY := data→BoardHeight - data→GameBoardHeight;
  data→Board := media→create_full_board;
  data→CurrentShape := data→Board→create_sprite_set;
  data→ShapeStack := data→Board→create_sprite_set;
  data→NextShape := data→Board→create_sprite_set;
  data→Board→create_boundary(0);
  $<hh user=bottom> := data→Board→create_rectangle(data→GameBoardWidth, 10);
  $<hh user=bottom>→set_pos(data→GameBoardWidth / 2, data→BoardHeight + 5);
  data→RowChecker := data→Board→create_rectangle(data→GameBoardWidth, data→TileSize - 2);
  data→RowChecker→set_pos(data→GameBoardWidth / 2, data→GameBoardY - data→TileSize / 2);
  data→RowChecker→hide;
  data→ShapeStack→add($<hh user=bottom>);
  data→Board→post_to_wall;
  data→lnterval := 1000;
  data→ScoreSprite := data→Board→create_text(100, 20, 40, "0");
  data→ScoreSprite→set_pos(60, 20);
  data→PreviewX := data→GameBoardWidth + data→TileSize / 2 * 5;
  data→PreviewY := data→GameBoardY + data→TileSize / 2 * 5;
  data→FullRowCount := 0;
  data→ShapeCount := 0;
  data→Level := 0;
  data→Drop := false;
  data→Score := 0;
  code→preview;
  meta private;
}
var Board : Board {
}

Делаем отрисовку фигуры и задаём глобальную переменную, обозначающую текущую фигуру:

action drawShape() {
  foreach sprite in data→NextShape where true do {
    data→CurrentShape→add_from(data→NextShape, $sprite);
    $sprite→hide;
  }
  code→move(data→TileSize * - 7, - data→TileSize * 4, true);
  code→setShapePos;
  code→preview;
  data→Board→update_on_wall;
  meta private;
}
var CurrentShape : Sprite_Set {
}

Что ж помимо того, чтобы фигура отрисовывалась, необходимо ей управлять: перемещать влево, вправо, ускорять перемещение вниз, а также вращать:

action rotateShape() {
  ... ;
  code→rotateShapeBy(90);
  if code→collisionCheck then {
    code→rotateShapeBy( - 90);
  }
  else {
    $left := data→ShapeLeft;
    if $left < 0 then {
      code→moveShape($left * - 1, 0);
      if code→collisionCheck then {
        code→moveShape($left, 0);
        code→rotateShapeBy( - 90);
      }
    }
    $right := data→ShapeRight - data→GameBoardWidth;
    if $right > 0 then {
      code→moveShape($right * - 1, 0);
      if code→collisionCheck then {
        code→moveShape($right, 0);
        code→rotateShapeBy( - 90);
      }
    }
  }
  meta private;
}
var ShapeDefinition : Json_Object {
}

action moveShape(xd: Number, yd: Number) returns b1: Boolean {
  $b1 := code→move($xd, $yd, false);
  meta private;
}
var ShapeStack : Sprite_Set {
}

Добавим событие вызова цикла игры:

event gameloop() {
  code→gameLoop;
  meta private;
}

И опишем его:

action gameLoop() {
  $interval := data→lnterval;
  if data→Drop then {
    $interval := 80;
  }
  if data→LastMove→add_milliseconds($interval)→less(time→now) then {
    $collided := code→moveShape(0, data→TileSize);
    if $collided then {
      data→Drop := false;
      code→putShapeOnStack;
      data→ShapeCount := data→ShapeCount + 1;
      code→checkRows;
      data→Score := data→FullRowCount * 100 + data→ShapeCount * 10;
      if math→floor(data→Score / 1000) > data→Level then {
        data→lnterval := data→lnterval * 0.8;
        data→Level := data→Level + 1;
      }
      data→ScoreSprite→set_text(data→Score ∥ "");
      if data→GameOver then {
        $sprite := data→Board→create_text(460, 260, 80, "Game Over");
        ... ;
        $sprite→set_pos(240, 400);
        data→Board→update_on_wall;
        time→sleep(2);
        bazaar→post_leaderboard_score(data→Score);
        bazaar→post_leaderboard_to_wall;
      }
      else {
        code→drawShape;
      }
    }
    else {
      code;
    }
    data→LastMove := time→now;
    data→Board→update_on_wall;
  }
  meta private;
}

Проверка коллизий и добавление фигур на поле:

action collisionCheck() returns collision: Boolean {
  $collision := false;
  foreach sprite in data→CurrentShape where $collision→equals(false) do {
    $sprite_set := $sprite→overlap_with(data→ShapeStack);
    $collision := ($sprite_set→count > 0);
  }
  ... ;
  meta private;
}

action putShapeOnStack() {
  ... ;
  foreach sprite1 in data→CurrentShape where true do {
    data→ShapeStack→add_from(data→CurrentShape, $sprite1);
  }
  meta private;
}

Проверяем, не достигнута ли верхняя граница поля (в случае, если да, то игра проиграна):

action checkRows() {
  $y := data→RowChecker→y;
  if data→RowChecker→overlap_with(data→ShapeStack)→count > 0 then {
    data→GameOver := true;
  }
  else {
    while $y < data→BoardHeight do {
      data→RowChecker→set_y($y);
      $overlap := data→RowChecker→overlap_with(data→ShapeStack);
      $max := 10;
      if($overlap→count = $max) then {
        code→blink($overlap, 5);
        foreach sprite in $overlap where true do {
          $sprite→hide;
          data→ShapeStack→remove($sprite);
        }
        foreach sprite1 in data→ShapeStack where true do {
          if $sprite1→y < $y then {
            $sprite1→move(0, data→TileSize);
          }
        }
        data→FullRowCount := data→FullRowCount + 1;
      }
      $y := $y + data→TileSize;
    }
  }
  data→RowChecker→set_y(data→GameBoardY - data→TileSize / 2);
  ... ;
  meta private;
}

Перемещаем фигуру, а также вращаем по тапу:

var RowChecker : Sprite {
}
var ShapeLeft : Number {
}
var ShapeRight : Number {
}
action setShapePos() {
  data→ShapeLeft := data→GameBoardWidth;
  data→ShapeRight := 0;
  foreach sprite in data→CurrentShape where true do {
    $w := data→TileSize / 2;
    $left := $sprite→x - $w;
    $right := $sprite→x + $w;
    if $left < data→ShapeLeft then {
      data→ShapeLeft := $left;
    }
    if $right > data→ShapeRight then {
      data→ShapeRight := $right;
    }
  }
  meta private;
}
var LastMove : DateTime {
}
event swipe_boardu003A_Board(x: Number, y: Number, delta_x: Number, delta_y: Number) {
  code→swipeShape($delta_x, $delta_y);
  meta private;
}
var lnterval : Number {
}

event tap_boardu003A_Board(x: Number, y: Number) {
  code→rotateShape;
  meta private;
}

action drawLines() {
  $w := data→TileSize;
  $pic := media→create_picture(data→Board→width, data→Board→height);
  $y := data→GameBoardY;
  for 0 ≤ i < 11 do {
    $pic→draw_line($i * $w, $y, $i * $w, data→BoardHeight, colors→dark_gray, 1);
  }
  for 0 ≤ i1 < 21 do {
    $pic→draw_line(0, $i1 * $w + $y, data→GameBoardWidth, $i1 * $w + $y, colors→dark_gray, 1);
  }
  data→Board→set_background_picture($pic);
  meta private;
}
var BoardHeight : Number {
}
var GameBoardHeight : Number {
}
var GameBoardWidth : Number {
}
var TileSize : Number {
}
var GameBoardY : Number {
}
var ScoreSprite : Sprite {
}

action rotateShapeBy(degree: Number) {
  $r := $degree * math→u03C0 / 180;
  $centerSprite := data→CurrentShape→at(0);
  $yo := $centerSprite→y;
  $xo := $centerSprite→x;
  foreach sprite in data→CurrentShape where $sprite→color→equals(colors→purple)→equals(false) do {
    $sprite→set_x($sprite→x - $xo);
    $sprite→set_y($sprite→y - $yo);
    $x1 := ($sprite→x * math→cos($r)) + ($sprite→y * math→sin($r) * - 1);
    $y1 := ($sprite→x * math→sin($r)) + ($sprite→y * math→cos($r));
    $sprite→set_x($x1 + $xo);
    $sprite→set_y($y1 + $yo);
  }
  code→setShapePos;
  meta private;
}

event tap_sprite_in_ShapeStack(sprite: Sprite, index_in_set: Number, x: Number, y: Number) {
  code→rotateShape;
  meta private;
}

event tap_sprite_in_CurrentShape(sprite: Sprite, index_in_set: Number, x: Number, y: Number) {
  code→rotateShape;
  meta private;
}

action swipeShape(delta_x: Number, delta_y: Number) {
  if $delta_y > 100 then {
    data→Drop := true;
  }
  else {
    if $delta_x > 100 and data→ShapeRight < data→GameBoardWidth - data→TileSize then {
      code→moveShape(data→TileSize * 2, 0);
    }
    else {
      if $delta_x > 15 and data→ShapeRight < data→GameBoardWidth then {
        code→moveShape(data→TileSize, 0);
      }
    }
    if $delta_x < - 100 and data→ShapeLeft > 0 + data→TileSize then {
      code→moveShape(data→TileSize * - 2, 0);
    }
    else {
      if $delta_x < - 15 and data→ShapeLeft > 0 then {
        code→moveShape( - data→TileSize, 0);
      }
    }
  }
  data→Board→evolve;
  data→Board→update_on_wall;
  meta private;
}

event swipe_sprite_in_CurrentShape(sprite: Sprite, index_in_set: Number, x: Number, y: Number, delta_x: Number, delta_y: Number) {
  code→swipeShape($delta_x, $delta_y);
  meta private;
}

event swipe_sprite_in_ShapeStack(sprite: Sprite, index_in_set: Number, x: Number, y: Number, delta_x: Number, delta_y: Number) {
  code→swipeShape($delta_x, $delta_y);
  meta private;
}

Что ж, осталось, совсем чуть-чуть. Добавляем превью для следующей фигуры:

action preview() {
  code→loadShape(data→PreviewX, data→PreviewY, math→rand(7));
  meta private;
}

action loadShape(x: Number, y: Number, shape: Number) {
  $c := colors→blue;
  if $shape = 1 then {
    $c := colors→red;
  }
  if $shape = 2 then {
    $c := colors→green;
  }
  if $shape = 3 then {
    $c := colors→yellow;
  }
  if $shape = 4 then {
    $c := colors→cyan;
  }
  if $shape = 5 then {
    $c := colors→orange;
  }
  if $shape = 6 then {
    $c := colors→purple;
  }
  $halfSize := data→TileSize / 2;
  foreach json in data→ShapeDefinition→field("Shapes")→at($shape)→field("Tiles") where true do {
    $sprite := data→Board→create_rectangle(data→TileSize, data→TileSize);
    $sprite→set_pos($x, $y);
    $sprite→move($json→number("x") * data→TileSize, $json→number("y") * data→TileSize);
    $sprite→set_color($c);
    $sprite→show;
    data→NextShape→add($sprite);
  }
  meta private;
}
var PreviewX : Number {
}
var PreviewY : Number {
}
var NextShape : Sprite_Set {
}
var GameOver : Boolean {
}

И добавляем задержку и мигание при составлении фигур в одну линию:

action sleep(milliseconds: Number) {
  $dt := time→now→add_milliseconds($milliseconds);
  while $dt→greater_or_equal(time→now) do {
    skip;
  }
  meta private;
}

action blink(sprite_set1: Sprite_Set, times: Number) {
  for 0 ≤ i < $times do {
    foreach sprite in $sprite_set1 where true do {
      if $sprite→is_visible then {
        $sprite→hide;
      }
      else {
        $sprite→show;
      }
    }
    data→Board→update_on_wall;
    code→sleep(200);
  }
  meta private;
}

action move(xd: Number, yd: Number, start: Boolean) returns b1: Boolean {
  foreach sprite in data→CurrentShape where true do {
    $sprite→move($xd, $yd);
    if $sprite→is_visible→equals(false) and $sprite→y > data→GameBoardY then {
      $sprite→show;
    }
  }
  $b1 := code→collisionCheck();
  if $b1 then {
    if $start then {
      $xd := 0;
      $yd := data→TileSize;
    }
    foreach sprite1 in data→CurrentShape where true do {
      $sprite1→move( - $xd, - $yd);
    }
  }
  code→setShapePos;
  meta private;
}
var FullRowCount : Number {
}
var ShapeCount : Number {
}
var Score : Number {
}
var Level : Number {
}
var Drop : Boolean {
}

Вуаля, готово. Результат будет таким:

Разрабатываем для Windows Phone под Windows Phone (на примере тетриса) Полезные ссылки:

Автор: III


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js