Вю - это Королевская игра УР


Фон

После обучения Котлин для появления кода, в декабре, я начал искать в кросс-компиляции Котлин для JVM и в JavaScript. Затем я написал игрового сервера в Котлин, а также простая игра, реализация игры, известной как Королевская игра УР. Логика игры сама по себе не очень хорошо без красивого клиента, чтобы воспроизвести его (мало кто любит отправлять данные вручную). Поэтому я решил сделать один в то, что стали моими любимыми JavaScript-фреймворк (у всех должна быть одна, верно?).

Репозитории, содержащие оба клиент и сервер можно найти здесь: https://github.com/Zomis/Server2

Играть в игру

Теперь вы можете играть в Королевская игра УР с сервера (простой ИИ просто делать случайные ходы также можно играть против) или без сервера. (Если вы не можете заставить сервер работать, играть в версию без сервера).

Обратите внимание, что эти материалы будут постоянно обновляться и может не отражать код в этот вопрос.

Правила королевской игры УР

Вернее, мои правила.

Два игрока борются за то, чтобы быть первым игроком, который рас все их 7 штук к выходу.

Фигуры ходят как этот:

v<<1 E<<    1 = First tile
>>>>>>>|    E = Exit
^<<2 E<<
  • Только игрок 1 может использовать в верхнем ряду, только 2 игрока могут использовать в нижнем ряду. Оба игрока разделяют среднем ряду.
  • Первая плитка для игрока 1 является " 1 " в верхнем ряду. Первую плитку игрока 2 является '2' в нижнем ряду.
  • Игроки по очереди в прокат четыре логических кубиков. Затем вы перемещаете кусок целый ряд шагов, что равняется сумме этих четырех булевых.
  • Пять плиток помечены цветами. Когда кусок земли на цветке игрок снова раскатать.
  • Пока плитки на цветок, другая часть не может выбить (только для середины цветка).

Основные Вопросы

  • У меня слишком много / слишком мало компонентов? Я стремлюсь, чтобы сделать несколько других игр в Vue, так что я хотел бы сделать вещи повторно.
  • Как мои навыки вю?
  • Можно ли что-то сделать лучше, как я использую вю?
  • Я нигде рядом с UX-дизайнер, но как это пользовательский опыт?
  • Любые другие отзывы также приветствуются.

Код

Некоторые код, который не входит ниже:

  • require("../../../games-js/web/games-js"): Это Котлин код для игры модели. Это код, который был transpiled в JavaScript из Котлин.
  • import Socket from "../socket"это вспомогательный класс для обработки потенциал с WebSocket соединение. Приведенный ниже код проверяет, если сокет подключен и может обрабатывать оба сценария.

RoyalGameOfUR.вю

<template>
  <div>
    <h1>{{ game }} : {{ gameId }}</h1>
    <div>
      <div>{{ gameOverMessage }}</div>
    </div>
    <div class="board-parent">
      <UrPlayerView v-bind:game="ur" v-bind:playerIndex="0"
        :gamePieces="gamePieces"
        :onPlaceNewHighlight="onPlaceNewHighlight"
        :mouseleave="mouseleave"
        :onPlaceNew="placeNew" />

      <div class="ur-board">
        <div class="ur-pieces-bg">
          <div v-for="idx in 20" class="piece piece-bg">
          </div>
          <div class="piece-black" style="grid-area: 1 / 5 / 2 / 7"></div>
          <div class="piece-black" style="grid-area: 3 / 5 / 4 / 7"></div>
        </div>
        <div class="ur-pieces-flowers">
          <UrFlower :x="0" :y="0" />
          <UrFlower :x="3" :y="1" />
          <UrFlower :x="0" :y="2" />
          <UrFlower :x="6" :y="0" />
          <UrFlower :x="6" :y="2" />
        </div>

        <div class="ur-pieces-player">
          <transition name="fade">
            <UrPiece v-if="destination !== null" :piece="destination" class="piece highlighted"
            :mouseover="doNothing" :mouseleave="doNothing"
            :class="{['piece-' + destination.player]: true}">
            </UrPiece>
          </transition>
          <UrPiece v-for="piece in playerPieces"
            :key="piece.key"
            class="piece"
            :mouseover="mouseover" :mouseleave="mouseleave"
            :class="{['piece-' + piece.player]: true, 'moveable':
              ur.isMoveTime && piece.player == ur.currentPlayer &&
              ur.canMove_qt1dr2$(ur.currentPlayer, piece.position, ur.roll)}"
            :piece="piece"
            :onclick="onClick">
          </UrPiece>
        </div>
      </div>
      <UrPlayerView v-bind:game="ur" v-bind:playerIndex="1"
       :gamePieces="gamePieces"
       :onPlaceNewHighlight="onPlaceNewHighlight"
       :mouseleave="mouseleave"
       :onPlaceNew="placeNew" />
      <UrRoll :roll="lastRoll" :usable="ur.roll < 0 && canControlCurrentPlayer" :onDoRoll="onDoRoll" />
    </div>
  </div>
</template>

<script>
import Socket from "../socket";
import UrPlayerView from "./ur/UrPlayerView";
import UrPiece from "./ur/UrPiece";
import UrRoll from "./ur/UrRoll";
import UrFlower from "./ur/UrFlower";

var games = require("../../../games-js/web/games-js");
if (typeof games["games-js"] !== "undefined") {
  // This is needed when doing a production build, but is not used for `npm run dev` locally.
  games = games["games-js"];
}
let urgame = new games.net.zomis.games.ur.RoyalGameOfUr_init();
console.log(urgame.toString());

function piecesToObjects(array, playerIndex) {
  var playerPieces = array[playerIndex].filter(i => i > 0 && i < 15);
  var arrayCopy = []; // Convert Int32Array to Object array
  playerPieces.forEach(it => arrayCopy.push(it));

  function mapping(position) {
    var y = playerIndex == 0 ? 0 : 2;
    if (position > 4 && position < 13) {
      y = 1;
    }
    var x =
      y == 1
        ? position - 5
        : position <= 4 ? 4 - position : 4 + 8 + 8 - position;
    return {
      x: x,
      y: y,
      player: playerIndex,
      key: playerIndex + "_" + position,
      position: position
    };
  }
  for (var i = 0; i < arrayCopy.length; i++) {
    arrayCopy[i] = mapping(arrayCopy[i]);
  }
  return arrayCopy;
}

export default {
  name: "RoyalGameOfUR",
  props: ["yourIndex", "game", "gameId"],
  data() {
    return {
      highlighted: null,
      lastRoll: 0,
      gamePieces: [],
      playerPieces: [],
      lastMove: 0,
      ur: urgame,
      gameOverMessage: null
    };
  },
  created() {
    if (this.yourIndex < 0) {
      Socket.send(
        `v1:{ "type": "observer", "game": "${this.game}", "gameId": "${
          this.gameId
        }", "observer": "start" }`
      );
    }
    Socket.$on("type:PlayerEliminated", this.messageEliminated);
    Socket.$on("type:GameMove", this.messageMove);
    Socket.$on("type:GameState", this.messageState);
    Socket.$on("type:IllegalMove", this.messageIllegal);
    this.playerPieces = this.calcPlayerPieces();
  },
  beforeDestroy() {
    Socket.$off("type:PlayerEliminated", this.messageEliminated);
    Socket.$off("type:GameMove", this.messageMove);
    Socket.$off("type:GameState", this.messageState);
    Socket.$off("type:IllegalMove", this.messageIllegal);
  },
  components: {
    UrPlayerView,
    UrRoll,
    UrFlower,
    UrPiece
  },
  methods: {
    doNothing: function() {},
    action: function(name, data) {
      if (Socket.isConnected()) {
        let json = `v1:{ "game": "UR", "gameId": "${
          this.gameId
        }", "type": "move", "moveType": "${name}", "move": ${data} }`;
        Socket.send(json);
      } else {
        console.log(
          "Before Action: " + name + ":" + data + " - " + this.ur.toString()
        );
        if (name === "roll") {
          let rollResult = this.ur.doRoll();
          this.rollUpdate(rollResult);
        } else {
          console.log(
            "move: " + name + " = " + data + " curr " + this.ur.currentPlayer
          );
          var moveResult = this.ur.move_qt1dr2$(
            this.ur.currentPlayer,
            data,
            this.ur.roll
          );
          console.log("result: " + moveResult);
          this.playerPieces = this.calcPlayerPieces();
        }
        console.log(this.ur.toString());
      }
    },
    placeNew: function(playerIndex) {
      if (this.canPlaceNew) {
        this.action("move", 0);
      }
    },
    onClick: function(piece) {
      if (piece.player !== this.ur.currentPlayer) {
        return;
      }
      if (!this.ur.isMoveTime) {
        return;
      }
      console.log("OnClick in URView: " + piece.x + ", " + piece.y);
      this.action("move", piece.position);
    },
    messageEliminated(e) {
      console.log(`Recieved eliminated: ${JSON.stringify(e)}`);
      this.gameOverMessage = e;
    },
    messageMove(e) {
      console.log(`Recieved move: ${e.moveType}: ${e.move}`);
      if (e.moveType == "move") {
        this.ur.move_qt1dr2$(this.ur.currentPlayer, e.move, this.ur.roll);
      }
      this.playerPieces = this.calcPlayerPieces();
      // A move has been done - check if it is my turn.
      console.log("After Move: " + this.ur.toString());
    },
    messageState(e) {
      console.log(`MessageState: ${e.roll}`);
      if (typeof e.roll !== "undefined") {
        this.ur.doRoll_za3lpa$(e.roll);
        this.rollUpdate(e.roll);
      }
      console.log("AfterState: " + this.ur.toString());
    },
    messageIllegal(e) {
      console.log("IllegalMove: " + JSON.stringify(e));
    },
    rollUpdate(rollValue) {
      this.lastRoll = rollValue;
    },
    onDoRoll() {
      this.action("roll", -1);
    },
    onPlaceNewHighlight(playerIndex) {
      if (playerIndex !== this.ur.currentPlayer) {
        return;
      }
      this.highlighted = { player: playerIndex, position: 0 };
    },
    mouseover(piece) {
      if (piece.player !== this.ur.currentPlayer) {
        return;
      }
      this.highlighted = piece;
    },
    mouseleave() {
      this.highlighted = null;
    },
    calcPlayerPieces() {
      let pieces = this.ur.piecesCopy;
      this.gamePieces = this.ur.piecesCopy;
      let obj0 = piecesToObjects(pieces, 0);
      let obj1 = piecesToObjects(pieces, 1);
      let result = [];
      for (var i = 0; i < obj0.length; i++) {
        result.push(obj0[i]);
      }
      for (var i = 0; i < obj1.length; i++) {
        result.push(obj1[i]);
      }
      console.log(result);
      return result;
    }
  },
  computed: {
    canControlCurrentPlayer: function() {
      return this.ur.currentPlayer == this.yourIndex || !Socket.isConnected();
    },
    destination: function() {
      if (this.highlighted === null) {
        return null;
      }
      if (!this.ur.isMoveTime) {
        return null;
      }
      if (
        !this.ur.canMove_qt1dr2$(
          this.ur.currentPlayer,
          this.highlighted.position,
          this.ur.roll
        )
      ) {
        return null;
      }
      let resultPosition = this.highlighted.position + this.ur.roll;
      let result = piecesToObjects(
        [[resultPosition], [resultPosition]],
        this.highlighted.player
      );
      return result[0];
    },
    canPlaceNew: function() {
      return (
        this.canControlCurrentPlayer &&
        this.ur.canMove_qt1dr2$(this.ur.currentPlayer, 0, this.ur.roll)
      );
    }
  }
};
</script>

<style>
.piece-0 {
  background-color: blue;
}

.ur-pieces-player .piece {
  margin: auto;
  width: 48px;
  height: 48px;
}

.piece-1 {
  background-color: red;
}

.piece-flower {
  opacity: 0.5;
  background-image: url('../assets/ur/flower.svg');
  margin: auto;
}

.board-parent {
  position: relative;
}

.piece-bg {
  background-color: white;
  border: 1px solid black;
}

.ur-board {
  position: relative;
  width: 512px;
  height: 192px;
  min-width: 512px;
  min-height: 192px;
  overflow: hidden;
  border: 12px solid #6D5720;
  border-radius: 12px;
  margin: auto;
}

.ur-pieces-flowers {
  z-index: 60;
}

.ur-pieces-flowers, .ur-pieces-player,
 .ur-pieces-bg {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(3, 1fr);
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.ur-pieces-player .piece {
  z-index: 70;
}

.piece {
  background-size: cover;
  z-index: 40;
  width: 100%;
  height: 100%;
}

.piece-black {
  background-color: #7f7f7f;
}

.player-view {
  width: 512px;
  height: 50px;
  margin: auto;
  display: flex;
  flex-flow: row;
  justify-content: space-between;
  align-items: center;
}

.side {
  display: flex;
  flex-flow: row;
}

.piece.highlighted {
  opacity: 0.5;
  box-shadow: 0 0 10px 8px black;
}

.side-out {
  flex-flow: row-reverse;
}

.moveable {
  cursor: pointer;
  animation: glow 1s infinite alternate;
}

@keyframes glow {
  from {
    box-shadow: 0 0 10px -10px #aef4af;
  }
  to {
    box-shadow: 0 0 10px 10px #aef4af;
  }
}

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}

</style>

UrFlower.вю

<template>
  <div class="piece piece-flower"
     v-bind:style="{ 'grid-area': (y+1) + '/' + (x+1) }">
  </div>
</template>
<script>
export default {
  name: "UrFlower",
  props: ["x", "y"]
};
</script>

UrPiece.вю

<template>
  <transition name="fade">
  <div class="piece"
    v-on:click="click(piece)"
    :class="piece.id"
    @mouseover="mouseover(piece)" @mouseleave="mouseleave()"
     v-bind:style="{ gridArea: (piece.y+1) + '/' + (piece.x+1) }">
  </div>
  </transition>
</template>
<script>
export default {
  name: "UrPiece",
  props: ["piece", "onclick", "mouseover", "mouseleave"],
  methods: {
    click: function(piece) {
      console.log(piece);
      this.onclick(piece);
    }
  }
};
</script>

UrPlayerView.вю

<template>
  <div class="player-view">
    <div class="side side-remaining">
      <div class="number">{{ remaining }}</div>
      <div class="pieces-container">
        <div v-for="n in remaining" class="piece-small pointer"
          :class="{ ['piece-' + playerIndex]: true, moveable: canPlaceNew && n == remaining }"
          @mouseover="onPlaceNewHighlight(playerIndex)" @mouseleave="mouseleave()"
          style="position: absolute; top: 6px;"
          :style="{ left: (n-1)*12 + 'px' }" v-on:click="placeNew()">
        </div>
      </div>
    </div>
    <transition name="fade">
      <div class="player-active-indicator" v-if="game.currentPlayer == playerIndex"></div>
    </transition>
    <div class="side side-out">
      <div class="number">{{ out }}</div>
      <div class="pieces-container">
        <div v-for="n in out" class="piece-small"
          :class="['piece-' + playerIndex]"
          style="position: absolute; top: 6px;"
          :style="{ right: (n-1)*12 + 'px' }">
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "UrPlayerView",
  props: [
    "game",
    "playerIndex",
    "onPlaceNew",
    "gamePieces",
    "onPlaceNewHighlight",
    "mouseleave"
  ],
  data() {
    return {};
  },
  methods: {
    placeNew: function() {
      this.onPlaceNew(this.playerIndex);
    }
  },
  computed: {
    remaining: function() {
      return this.gamePieces[this.playerIndex].filter(i => i === 0).length;
    },
    out: function() {
      return this.gamePieces[this.playerIndex].filter(i => i === 15).length;
    },
    canPlaceNew: function() {
      return (
        this.game.currentPlayer == this.playerIndex &&
        this.game.isMoveTime &&
        this.game.canMove_qt1dr2$(this.playerIndex, 0, this.game.roll)
      );
    }
  }
};
</script>
<style scoped>
.player-active-indicator {
  background: black;
  border-radius: 100%;
  width: 20px;
  height: 20px;
}

.number {
  margin: 2px;
  font-weight: bold;
  font-size: 2em;
}

.piece-small {
  background-size: cover;
  width: 24px;
  height: 24px;
  border: 1px solid black;
}

.pieces-container {
  position: relative;
}
</style>

UrRoll.вю

<template>
  <div class="ur-roll">
    <div class="ur-dice" @click="onclick()" :class="{ moveable: usable }">
      <div v-for="i in 4" class="ur-die">
        <div v-if="rolls[i - 1]" class="ur-die-filled"></div>
      </div>
    </div>
    <span>{{ roll }}</span>
  </div>

</template>
<script>
function shuffle(array) {
  // https://stackoverflow.com/a/2450976/1310566
  var currentIndex = array.length,
    temporaryValue,
    randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {
    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

export default {
  name: "UrRoll",
  props: ["roll", "usable", "onDoRoll"],
  data() {
    return { rolls: [false, false, false, false] };
  },
  watch: {
    roll: function(newValue, oldValue) {
      console.log("Set roll to " + newValue);
      if (newValue < 0) {
        return;
      }
      this.rolls.fill(false);
      this.rolls.fill(true, 0, newValue);
      console.log(this.rolls);
      shuffle(this.rolls);
      console.log("After shuffle:");
      console.log(this.rolls);
    }
  },
  methods: {
    onclick: function() {
      this.onDoRoll();
    }
  }
};
</script>
<style scoped>
.ur-roll {
  margin-top: 10px;
}

.ur-roll span {
  font-size: 2em;
  font-weight: bold;
}

.ur-dice {
  width: 320px;
  height: 64px;
  margin: 5px auto 5px auto;
  display: flex;
  justify-content: space-between;
}

.ur-die-filled {
  background: black;
  border-radius: 100%;
  width: 20%;
  height: 20%;
}

.ur-die {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 64px;
  border: 1px solid black;
  border-radius: 12px;
}
</style>


Комментарии
2 ответа

предупреждение: сырный мем с плохой каламбур ниже - Если вам не нравятся эти, то, пожалуйста, пропустите его...


Ermagherd 2

Ответы Вопрос


У меня слишком много / слишком мало компонентов? Я стремлюсь, чтобы сделать несколько других игр в Vue, так что я хотел бы сделать вещи повторно.

Я думаю, что текущие компоненты делятся хорошо. Существующие компоненты смысла.


Как мои навыки вю?

Использование Вуе выглядит хорошо. Есть несколько общих аспектов в JS, что у меня обратной связи (см. ниже, под последний "вопрос"), но использование компонентов Vue и другие конструкции выглядит хорошо.


Можно ли что-то сделать лучше, как я использую вю?

Имея в виду, я не эксперт VueJS пользователей и только работал с ним на небольших проектах в прошлом году, я не могу действительно думать ни о чем... если вы действительно хотели, вы могли бы рассмотреть, используя слоты как-то, или событие автобус, если компоненты стали более разделены, но это может быть не nessary поскольку все данные, содержащиеся в основной RoyalGameOfUR компонента.

Если я вспомню что-то еще, я, конечно, обновлю этот ответ.


Я нигде рядом с UX-дизайнер, но как это пользовательский опыт?

Макет компоненты игры-это хорошо, хотя было бы полезно иметь больше текста, запрашивая у пользователя, что делать, или, по крайней мере, правила игры и играть где-то (например, в текстовом поле, связанном на другую страницу и т. д.). В этом же ключе, я вижу непойманное исключение в консоли, если пользователь нажимает на кости, когда это не время, чтобы свернуть. Можно поймать исключение и предупредить пользователя о том, что произошло.


Любые другие отзывы также приветствуются.

Отзывы

Вау, что это действительно шикарное приложение! Молодец! Я не использовал grid стили, но надеюсь в будущем.

Я заметила, что после прокатки кости, выбирая кусок от стека, не важно какой игрок игрок - я могу нажать на любой стек (правда, только кусок от стека текущего игрока будут перемещены).

Я заметил ошибку после того как о this.onclick is not defined но я не видел пути, чтобы воспроизвести его. Если я снова его увижу, я дам вам знать.

Предложения

Яш

let & const

Я вижу код использует let в нескольких местах, но в противном случае просто var. Было бы разумно, чтобы начать использовать const где значение хранится, но никогда не переназначены - то использовать let если повторное назначение необходимо. Используя var вне функция объявляется глобальная переменная1...я только один из тех в вашем посте (т. е. var games) но если есть другие места, где вы хотели переменную в другой файл games тогда это может привести к непреднамеренной значение более-писать.

Массив копирование

В piecesToObjects()Я вижу эти строки:


var arrayCopy = []; // Convert Int32Array to Object array
playerPieces.forEach(it => arrayCopy.push(it));

Вы могли бы использовать Array.from() чтобы скопировать массив, а затем использовать array.map() для вызова mapping() вместо использования for петли. Изначально я думал, что forEach могут быть устранены, но есть необходимость получить обычный массив вместо типизированного массива (т. е. Int32Array). Если массив копируются (т. е. array) был обычный массив, то вы, вероятно, могли бы просто использовать .map() - см. см. Этот тест jsperf , чтобы увидеть, как гораздо быстрее, что сопоставление может быть.

return Array.from(playerPieces).map(mapping);

И эту функцию mapping может быть вытащена из piecesToObjects если playerIndex принимает в качестве первого параметра, а затем playerIndex могут быть отправлены на каждой итерации, используя функцию.привязать() - т. е. использовать частично примененную функцию.

return Array.from(playerPieces).map(mapping.bind(null, playerIndex));

Вложенный тернарный оператор

Принимая во внимание, что это может только быть поддержан, если кто-то еще хотел обновить код, что человек может найти в строке ниже-менее читабельный, чем несколько обычных if блоки. Мой бывший начальник имел правило: не более одного тернарного оператора в одно выражение, особенно если его сделали более ~100 символов.


var x =
y == 1
? position - 5
: position <= 4 ? 4 - position : 4 + 8 + 8 - position;

Что-то немного более читабельным может быть:

var x;
if (y == 1) {
x = position - 5;
}
else {
x = position <= 4 ? 4 - position : 4 + 8 + 8 - position;
}

0-основана цветок сетки районов

Зачем добавлять 1 к X и Y в шаблон UrFlower это? Возможно, вы так привыкли основе 0 индексы и хотел сохранить эти значения в разметке ортогональных с вашей стороны... эти цветы можно поставить к массив и петельные за помощью v-for... но по 5 цветов, что может быть чрезмерным...

УСБ

Встроенный стиль против УСБ

Есть статический встроенный стиль атрибутов в UrPlayerView.Вуэ - например :


div v-for="n in remaining" class="piece-small pointer"
:class="{ ['piece-' + playerIndex]: true, moveable: canPlaceNew && n == remaining }"
@mouseover="onPlaceNewHighlight(playerIndex)" @mouseleave="mouseleave()"
style="position: absolute; top: 6px;"

и


<div v-for="n in out" class="piece-small"
:class="['piece-' + playerIndex]"
style="position: absolute; top: 6px;"

Расположение и лучшие стили могут быть введены в существующий набор правил для .piece-small...

1https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#Description

8
ответ дан 5 апреля 2018 в 02:04 Источник Поделиться

[1]

Я стал больше знакомы с , так как я дал ответ еще в апреле, и понимаю теперь, что, потому что вы используете , такие услуги, как const, let и Стрелка функций, другими ус-6 характеристики могут быть использованы в качестве хорошо.

Например, следующие строки в shuffle() функции:


// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;

Можно упростить до одной строки, используя (массив) реструктуризующее присваивание

[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];

Я также предложил использовать Array.from() чтобы скопировать массив штук в piecesToObjects()и хотя это часть Эс-6 стандарт spread syntax может быть использован вместо вызова этой функции. Вместо окончательного заявления


return Array.from(playerPieces).map(mapping);

Вы должны быть в состоянии использовать, что распространение синтаксис:

return [...playerPieces].map(mapping);

1
ответ дан 25 ноября 2018 в 06:11 Источник Поделиться