Vue - It's the Royal Game of Ur











up vote
12
down vote

favorite
1












Background



After learning Kotlin for Advent of Code in December, I started looking into cross-compiling Kotlin for both the JVM and to JavaScript. Then I wrote a game server in Kotlin and also a simple game implementation of a game known as The Royal Game of Ur. Game logic by itself doesn't do much good though without a beautiful client to play it with (few people likes sending data manually). So I decided to make one in what have become my favorite JavaScript framework (everyone must have one, right?).



The repository containing both the client and the server can be found here: https://github.com/Zomis/Server2



Play the game



You can now play The Royal Game of UR with a server (a simple AI just making a random move is also available to play against) or without a server. (If you can't get the server to work, play the version without a server).



Please note that these will be updated continuously and may not reflect the code in this question.



Rules of The Royal Game of Ur



Or rather, my rules.



Two players are fighting to be the first player who races all their 7 pieces to the exit.



The pieces walk like this:



v<<1 E<<    1 = First tile
>>>>>>>| E = Exit
^<<2 E<<



  • Only player 1 can use the top row, only player 2 can use the bottom row. Both players share the middle row.

  • The first tile for Player 1 is the '1' in the top row. Player 2's first tile is the '2' in the bottom row.

  • Players take turns in rolling the four boolean dice. Then you move a piece a number of steps that equals the sum of these four booleans.

  • Five tiles are marked with flowers. When a piece lands on a flower the player get to roll again.

  • As long as a tile is on a flower another piece may not knock it out (only relevant for the middle flower).


Main Questions




  • Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.

  • How are my Vue skills?

  • Can anything be done better with regards to how I am using Vue?

  • I am nowhere near a UX-designer, but how is the user experience?

  • Any other feedback also welcome.


Code



Some code that is not included below:





  • require("../../../games-js/web/games-js"): This is the Kotlin code for the game model. This is code that has been transpiled to JavaScript from Kotlin.


  • import Socket from "../socket": This is an utility class for handling the potential WebSocket connection. The code below is checking if the Socket is connected and can handle both scenarios.


RoyalGameOfUR.vue



<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.vue



<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.vue



<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.vue



<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.vue



<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>









share|improve this question




























    up vote
    12
    down vote

    favorite
    1












    Background



    After learning Kotlin for Advent of Code in December, I started looking into cross-compiling Kotlin for both the JVM and to JavaScript. Then I wrote a game server in Kotlin and also a simple game implementation of a game known as The Royal Game of Ur. Game logic by itself doesn't do much good though without a beautiful client to play it with (few people likes sending data manually). So I decided to make one in what have become my favorite JavaScript framework (everyone must have one, right?).



    The repository containing both the client and the server can be found here: https://github.com/Zomis/Server2



    Play the game



    You can now play The Royal Game of UR with a server (a simple AI just making a random move is also available to play against) or without a server. (If you can't get the server to work, play the version without a server).



    Please note that these will be updated continuously and may not reflect the code in this question.



    Rules of The Royal Game of Ur



    Or rather, my rules.



    Two players are fighting to be the first player who races all their 7 pieces to the exit.



    The pieces walk like this:



    v<<1 E<<    1 = First tile
    >>>>>>>| E = Exit
    ^<<2 E<<



    • Only player 1 can use the top row, only player 2 can use the bottom row. Both players share the middle row.

    • The first tile for Player 1 is the '1' in the top row. Player 2's first tile is the '2' in the bottom row.

    • Players take turns in rolling the four boolean dice. Then you move a piece a number of steps that equals the sum of these four booleans.

    • Five tiles are marked with flowers. When a piece lands on a flower the player get to roll again.

    • As long as a tile is on a flower another piece may not knock it out (only relevant for the middle flower).


    Main Questions




    • Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.

    • How are my Vue skills?

    • Can anything be done better with regards to how I am using Vue?

    • I am nowhere near a UX-designer, but how is the user experience?

    • Any other feedback also welcome.


    Code



    Some code that is not included below:





    • require("../../../games-js/web/games-js"): This is the Kotlin code for the game model. This is code that has been transpiled to JavaScript from Kotlin.


    • import Socket from "../socket": This is an utility class for handling the potential WebSocket connection. The code below is checking if the Socket is connected and can handle both scenarios.


    RoyalGameOfUR.vue



    <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.vue



    <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.vue



    <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.vue



    <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.vue



    <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>









    share|improve this question


























      up vote
      12
      down vote

      favorite
      1









      up vote
      12
      down vote

      favorite
      1






      1





      Background



      After learning Kotlin for Advent of Code in December, I started looking into cross-compiling Kotlin for both the JVM and to JavaScript. Then I wrote a game server in Kotlin and also a simple game implementation of a game known as The Royal Game of Ur. Game logic by itself doesn't do much good though without a beautiful client to play it with (few people likes sending data manually). So I decided to make one in what have become my favorite JavaScript framework (everyone must have one, right?).



      The repository containing both the client and the server can be found here: https://github.com/Zomis/Server2



      Play the game



      You can now play The Royal Game of UR with a server (a simple AI just making a random move is also available to play against) or without a server. (If you can't get the server to work, play the version without a server).



      Please note that these will be updated continuously and may not reflect the code in this question.



      Rules of The Royal Game of Ur



      Or rather, my rules.



      Two players are fighting to be the first player who races all their 7 pieces to the exit.



      The pieces walk like this:



      v<<1 E<<    1 = First tile
      >>>>>>>| E = Exit
      ^<<2 E<<



      • Only player 1 can use the top row, only player 2 can use the bottom row. Both players share the middle row.

      • The first tile for Player 1 is the '1' in the top row. Player 2's first tile is the '2' in the bottom row.

      • Players take turns in rolling the four boolean dice. Then you move a piece a number of steps that equals the sum of these four booleans.

      • Five tiles are marked with flowers. When a piece lands on a flower the player get to roll again.

      • As long as a tile is on a flower another piece may not knock it out (only relevant for the middle flower).


      Main Questions




      • Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.

      • How are my Vue skills?

      • Can anything be done better with regards to how I am using Vue?

      • I am nowhere near a UX-designer, but how is the user experience?

      • Any other feedback also welcome.


      Code



      Some code that is not included below:





      • require("../../../games-js/web/games-js"): This is the Kotlin code for the game model. This is code that has been transpiled to JavaScript from Kotlin.


      • import Socket from "../socket": This is an utility class for handling the potential WebSocket connection. The code below is checking if the Socket is connected and can handle both scenarios.


      RoyalGameOfUR.vue



      <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.vue



      <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.vue



      <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.vue



      <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.vue



      <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>









      share|improve this question















      Background



      After learning Kotlin for Advent of Code in December, I started looking into cross-compiling Kotlin for both the JVM and to JavaScript. Then I wrote a game server in Kotlin and also a simple game implementation of a game known as The Royal Game of Ur. Game logic by itself doesn't do much good though without a beautiful client to play it with (few people likes sending data manually). So I decided to make one in what have become my favorite JavaScript framework (everyone must have one, right?).



      The repository containing both the client and the server can be found here: https://github.com/Zomis/Server2



      Play the game



      You can now play The Royal Game of UR with a server (a simple AI just making a random move is also available to play against) or without a server. (If you can't get the server to work, play the version without a server).



      Please note that these will be updated continuously and may not reflect the code in this question.



      Rules of The Royal Game of Ur



      Or rather, my rules.



      Two players are fighting to be the first player who races all their 7 pieces to the exit.



      The pieces walk like this:



      v<<1 E<<    1 = First tile
      >>>>>>>| E = Exit
      ^<<2 E<<



      • Only player 1 can use the top row, only player 2 can use the bottom row. Both players share the middle row.

      • The first tile for Player 1 is the '1' in the top row. Player 2's first tile is the '2' in the bottom row.

      • Players take turns in rolling the four boolean dice. Then you move a piece a number of steps that equals the sum of these four booleans.

      • Five tiles are marked with flowers. When a piece lands on a flower the player get to roll again.

      • As long as a tile is on a flower another piece may not knock it out (only relevant for the middle flower).


      Main Questions




      • Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.

      • How are my Vue skills?

      • Can anything be done better with regards to how I am using Vue?

      • I am nowhere near a UX-designer, but how is the user experience?

      • Any other feedback also welcome.


      Code



      Some code that is not included below:





      • require("../../../games-js/web/games-js"): This is the Kotlin code for the game model. This is code that has been transpiled to JavaScript from Kotlin.


      • import Socket from "../socket": This is an utility class for handling the potential WebSocket connection. The code below is checking if the Socket is connected and can handle both scenarios.


      RoyalGameOfUR.vue



      <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.vue



      <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.vue



      <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.vue



      <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.vue



      <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>






      javascript game css ecmascript-6 vue.js






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 25 at 6:29









      Sᴀᴍ Onᴇᴌᴀ

      7,88561750




      7,88561750










      asked Mar 30 at 21:40









      Simon Forsberg

      48.5k7128286




      48.5k7128286






















          2 Answers
          2






          active

          oldest

          votes

















          up vote
          7
          down vote



          accepted










          warning: cheesy meme with bad pun below - if you don't like those, then please skip it...




          Ermagherd
          2




          Question responses




          Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.




          I think the current components are divided well. The existing components make sense.




          How are my Vue skills?




          Usage of Vue looks good. There are a few general JS aspects that I have feedback for (see below, under last "question") but usage of Vue components and other constructs looks good.




          Can anything be done better with regards to how I am using Vue?




          Bearing in mind I am not an expert VueJS user and have only been working with it on small projects in the past year, I can't really think of anything... If you really wanted you could consider using slots somehow, or an Event bus if the components became more separated but that might not be nessary since everything is contained in the main RoyalGameOfUR component.



          If I think of anything else, I will surely update this answer.




          I am nowhere near a UX-designer, but how is the user experience?




          The layout of the game components is okay, though it would be helpful to have more text prompting the user what to do, or at least the rules and game play instructions somewhere (e.g. in a text box, linked to another page, etc.). In the same vain, I see an uncaught exception in the console if the user clicks the dice when it isn't time to roll. One could catch the exception and alert the user about what happened.




          Any other feedback also welcome.




          Feedback



          Wow that is a really elegant application! Well done! I haven't used the grid styles yet but hope to in the future.



          I did notice that after rolling the dice, when selecting a piece from a stack, it doesn't matter which player is the current player - I can click on either stack (though only a piece from the current player's stack will get moved).



          I did notice an error once about this.onclick is not defined but I didn't observe the path to reproduce it. If I see it again I will let you know.



          Suggestions



          JS




          let & const



          I see the code utilizes let in a few places but otherwise just var. It would be wise to start using const anywhere a value is stored but never re-assigned - then use let if re-assignment is necessary. Using var outside of a function declares a global variable1...I only spot one of those in your post (i.e. var games) but if there were other places where you wanted a variable in another file called games then this could lead to unintentional value over-writing.



          Array copying



          In piecesToObjects(), I see these lines:




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



          You could utilize Array.from() to copy the array, then use array.map() to call mapping() instead of using the for loop. Originally I was thinking that the forEach could be eliminated but there is a need to get a regular array instead of the typed array (i.e. Int32Array). If the array being copied (i.e. array) was a regular array, then you likely could just use .map() - see this jsPerf to see how much quicker that mapping could be.



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


          And that function mapping could be pulled out of piecesToObjects if playerIndex is accepted as the first parameter, and then playerIndex can be sent on each iteration using Function.bind() - i.e. using a partially applied function.



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


          Nested Ternary operator



          Bearing in mind that this might just be maintained by you, if somebody else wanted to update the code, that person might find the line below less readable than several normal if blocks. My former supervisor had a rule: no more than one ternary operator in one expression - especially if it made the line longer than ~100 characters.




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



          Something a little more readable might be:



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


          0-based Flower grid areas



          Why add 1 to the x and y values in UrFlower's template? Perhaps you are so used to 0-based indexes and wanted to keep those values in the markup orthogonal with your ways... Those flowers could be put unto an array and looped over using v-for... but for 5 flowers that might be too excessive...



          CSS



          Inline style vs CSS



          There are static inline style attributes in UrPlayerView.vue - e.g. :




          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;"



          and




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



          The position and top styles could be put into the existing ruleset for .piece-small...



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






          share|improve this answer



















          • 1




            I think the Vue <style scoped> is not the same as a HTML 5 <style scoped>. Vue adds some data-things to only apply the style to things from the same component. vue-loader.vuejs.org/en/features/scoped-css.html
            – Simon Forsberg
            Apr 6 at 8:28












          • Ah - my colleague setup the vue loader config before I started working on the project and I must have skipped over that section when learning about vue loader. Good correction!
            – Sᴀᴍ Onᴇᴌᴀ
            Apr 6 at 18:24


















          up vote
          1
          down vote













          [1]



          I have become more acquainted with ecmascript-6 since I gave the answer back in April, and realize now that because you are using ecmascript-6 features like const, let and arrow functions, other es-6 features could be used as well.



          For instance, the following lines in the shuffle() function:




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



          Could be simplified to a single line using (Array) destructuring assignment



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


          I also suggested using Array.from() to copy the array of pieces in piecesToObjects(), and while that is part of the es-6 standard, the spread syntax could be used instead of calling that function. Instead of a final statement like




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



          You should be able to use that spread syntax:



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





          share|improve this answer





















            Your Answer





            StackExchange.ifUsing("editor", function () {
            return StackExchange.using("mathjaxEditing", function () {
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            });
            });
            }, "mathjax-editing");

            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "196"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190896%2fvue-its-the-royal-game-of-ur%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            7
            down vote



            accepted










            warning: cheesy meme with bad pun below - if you don't like those, then please skip it...




            Ermagherd
            2




            Question responses




            Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.




            I think the current components are divided well. The existing components make sense.




            How are my Vue skills?




            Usage of Vue looks good. There are a few general JS aspects that I have feedback for (see below, under last "question") but usage of Vue components and other constructs looks good.




            Can anything be done better with regards to how I am using Vue?




            Bearing in mind I am not an expert VueJS user and have only been working with it on small projects in the past year, I can't really think of anything... If you really wanted you could consider using slots somehow, or an Event bus if the components became more separated but that might not be nessary since everything is contained in the main RoyalGameOfUR component.



            If I think of anything else, I will surely update this answer.




            I am nowhere near a UX-designer, but how is the user experience?




            The layout of the game components is okay, though it would be helpful to have more text prompting the user what to do, or at least the rules and game play instructions somewhere (e.g. in a text box, linked to another page, etc.). In the same vain, I see an uncaught exception in the console if the user clicks the dice when it isn't time to roll. One could catch the exception and alert the user about what happened.




            Any other feedback also welcome.




            Feedback



            Wow that is a really elegant application! Well done! I haven't used the grid styles yet but hope to in the future.



            I did notice that after rolling the dice, when selecting a piece from a stack, it doesn't matter which player is the current player - I can click on either stack (though only a piece from the current player's stack will get moved).



            I did notice an error once about this.onclick is not defined but I didn't observe the path to reproduce it. If I see it again I will let you know.



            Suggestions



            JS




            let & const



            I see the code utilizes let in a few places but otherwise just var. It would be wise to start using const anywhere a value is stored but never re-assigned - then use let if re-assignment is necessary. Using var outside of a function declares a global variable1...I only spot one of those in your post (i.e. var games) but if there were other places where you wanted a variable in another file called games then this could lead to unintentional value over-writing.



            Array copying



            In piecesToObjects(), I see these lines:




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



            You could utilize Array.from() to copy the array, then use array.map() to call mapping() instead of using the for loop. Originally I was thinking that the forEach could be eliminated but there is a need to get a regular array instead of the typed array (i.e. Int32Array). If the array being copied (i.e. array) was a regular array, then you likely could just use .map() - see this jsPerf to see how much quicker that mapping could be.



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


            And that function mapping could be pulled out of piecesToObjects if playerIndex is accepted as the first parameter, and then playerIndex can be sent on each iteration using Function.bind() - i.e. using a partially applied function.



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


            Nested Ternary operator



            Bearing in mind that this might just be maintained by you, if somebody else wanted to update the code, that person might find the line below less readable than several normal if blocks. My former supervisor had a rule: no more than one ternary operator in one expression - especially if it made the line longer than ~100 characters.




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



            Something a little more readable might be:



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


            0-based Flower grid areas



            Why add 1 to the x and y values in UrFlower's template? Perhaps you are so used to 0-based indexes and wanted to keep those values in the markup orthogonal with your ways... Those flowers could be put unto an array and looped over using v-for... but for 5 flowers that might be too excessive...



            CSS



            Inline style vs CSS



            There are static inline style attributes in UrPlayerView.vue - e.g. :




            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;"



            and




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



            The position and top styles could be put into the existing ruleset for .piece-small...



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






            share|improve this answer



















            • 1




              I think the Vue <style scoped> is not the same as a HTML 5 <style scoped>. Vue adds some data-things to only apply the style to things from the same component. vue-loader.vuejs.org/en/features/scoped-css.html
              – Simon Forsberg
              Apr 6 at 8:28












            • Ah - my colleague setup the vue loader config before I started working on the project and I must have skipped over that section when learning about vue loader. Good correction!
              – Sᴀᴍ Onᴇᴌᴀ
              Apr 6 at 18:24















            up vote
            7
            down vote



            accepted










            warning: cheesy meme with bad pun below - if you don't like those, then please skip it...




            Ermagherd
            2




            Question responses




            Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.




            I think the current components are divided well. The existing components make sense.




            How are my Vue skills?




            Usage of Vue looks good. There are a few general JS aspects that I have feedback for (see below, under last "question") but usage of Vue components and other constructs looks good.




            Can anything be done better with regards to how I am using Vue?




            Bearing in mind I am not an expert VueJS user and have only been working with it on small projects in the past year, I can't really think of anything... If you really wanted you could consider using slots somehow, or an Event bus if the components became more separated but that might not be nessary since everything is contained in the main RoyalGameOfUR component.



            If I think of anything else, I will surely update this answer.




            I am nowhere near a UX-designer, but how is the user experience?




            The layout of the game components is okay, though it would be helpful to have more text prompting the user what to do, or at least the rules and game play instructions somewhere (e.g. in a text box, linked to another page, etc.). In the same vain, I see an uncaught exception in the console if the user clicks the dice when it isn't time to roll. One could catch the exception and alert the user about what happened.




            Any other feedback also welcome.




            Feedback



            Wow that is a really elegant application! Well done! I haven't used the grid styles yet but hope to in the future.



            I did notice that after rolling the dice, when selecting a piece from a stack, it doesn't matter which player is the current player - I can click on either stack (though only a piece from the current player's stack will get moved).



            I did notice an error once about this.onclick is not defined but I didn't observe the path to reproduce it. If I see it again I will let you know.



            Suggestions



            JS




            let & const



            I see the code utilizes let in a few places but otherwise just var. It would be wise to start using const anywhere a value is stored but never re-assigned - then use let if re-assignment is necessary. Using var outside of a function declares a global variable1...I only spot one of those in your post (i.e. var games) but if there were other places where you wanted a variable in another file called games then this could lead to unintentional value over-writing.



            Array copying



            In piecesToObjects(), I see these lines:




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



            You could utilize Array.from() to copy the array, then use array.map() to call mapping() instead of using the for loop. Originally I was thinking that the forEach could be eliminated but there is a need to get a regular array instead of the typed array (i.e. Int32Array). If the array being copied (i.e. array) was a regular array, then you likely could just use .map() - see this jsPerf to see how much quicker that mapping could be.



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


            And that function mapping could be pulled out of piecesToObjects if playerIndex is accepted as the first parameter, and then playerIndex can be sent on each iteration using Function.bind() - i.e. using a partially applied function.



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


            Nested Ternary operator



            Bearing in mind that this might just be maintained by you, if somebody else wanted to update the code, that person might find the line below less readable than several normal if blocks. My former supervisor had a rule: no more than one ternary operator in one expression - especially if it made the line longer than ~100 characters.




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



            Something a little more readable might be:



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


            0-based Flower grid areas



            Why add 1 to the x and y values in UrFlower's template? Perhaps you are so used to 0-based indexes and wanted to keep those values in the markup orthogonal with your ways... Those flowers could be put unto an array and looped over using v-for... but for 5 flowers that might be too excessive...



            CSS



            Inline style vs CSS



            There are static inline style attributes in UrPlayerView.vue - e.g. :




            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;"



            and




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



            The position and top styles could be put into the existing ruleset for .piece-small...



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






            share|improve this answer



















            • 1




              I think the Vue <style scoped> is not the same as a HTML 5 <style scoped>. Vue adds some data-things to only apply the style to things from the same component. vue-loader.vuejs.org/en/features/scoped-css.html
              – Simon Forsberg
              Apr 6 at 8:28












            • Ah - my colleague setup the vue loader config before I started working on the project and I must have skipped over that section when learning about vue loader. Good correction!
              – Sᴀᴍ Onᴇᴌᴀ
              Apr 6 at 18:24













            up vote
            7
            down vote



            accepted







            up vote
            7
            down vote



            accepted






            warning: cheesy meme with bad pun below - if you don't like those, then please skip it...




            Ermagherd
            2




            Question responses




            Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.




            I think the current components are divided well. The existing components make sense.




            How are my Vue skills?




            Usage of Vue looks good. There are a few general JS aspects that I have feedback for (see below, under last "question") but usage of Vue components and other constructs looks good.




            Can anything be done better with regards to how I am using Vue?




            Bearing in mind I am not an expert VueJS user and have only been working with it on small projects in the past year, I can't really think of anything... If you really wanted you could consider using slots somehow, or an Event bus if the components became more separated but that might not be nessary since everything is contained in the main RoyalGameOfUR component.



            If I think of anything else, I will surely update this answer.




            I am nowhere near a UX-designer, but how is the user experience?




            The layout of the game components is okay, though it would be helpful to have more text prompting the user what to do, or at least the rules and game play instructions somewhere (e.g. in a text box, linked to another page, etc.). In the same vain, I see an uncaught exception in the console if the user clicks the dice when it isn't time to roll. One could catch the exception and alert the user about what happened.




            Any other feedback also welcome.




            Feedback



            Wow that is a really elegant application! Well done! I haven't used the grid styles yet but hope to in the future.



            I did notice that after rolling the dice, when selecting a piece from a stack, it doesn't matter which player is the current player - I can click on either stack (though only a piece from the current player's stack will get moved).



            I did notice an error once about this.onclick is not defined but I didn't observe the path to reproduce it. If I see it again I will let you know.



            Suggestions



            JS




            let & const



            I see the code utilizes let in a few places but otherwise just var. It would be wise to start using const anywhere a value is stored but never re-assigned - then use let if re-assignment is necessary. Using var outside of a function declares a global variable1...I only spot one of those in your post (i.e. var games) but if there were other places where you wanted a variable in another file called games then this could lead to unintentional value over-writing.



            Array copying



            In piecesToObjects(), I see these lines:




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



            You could utilize Array.from() to copy the array, then use array.map() to call mapping() instead of using the for loop. Originally I was thinking that the forEach could be eliminated but there is a need to get a regular array instead of the typed array (i.e. Int32Array). If the array being copied (i.e. array) was a regular array, then you likely could just use .map() - see this jsPerf to see how much quicker that mapping could be.



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


            And that function mapping could be pulled out of piecesToObjects if playerIndex is accepted as the first parameter, and then playerIndex can be sent on each iteration using Function.bind() - i.e. using a partially applied function.



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


            Nested Ternary operator



            Bearing in mind that this might just be maintained by you, if somebody else wanted to update the code, that person might find the line below less readable than several normal if blocks. My former supervisor had a rule: no more than one ternary operator in one expression - especially if it made the line longer than ~100 characters.




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



            Something a little more readable might be:



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


            0-based Flower grid areas



            Why add 1 to the x and y values in UrFlower's template? Perhaps you are so used to 0-based indexes and wanted to keep those values in the markup orthogonal with your ways... Those flowers could be put unto an array and looped over using v-for... but for 5 flowers that might be too excessive...



            CSS



            Inline style vs CSS



            There are static inline style attributes in UrPlayerView.vue - e.g. :




            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;"



            and




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



            The position and top styles could be put into the existing ruleset for .piece-small...



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






            share|improve this answer














            warning: cheesy meme with bad pun below - if you don't like those, then please skip it...




            Ermagherd
            2




            Question responses




            Do I have too many / too few components? I am aiming to make several other games in Vue so I like to make things re-useable.




            I think the current components are divided well. The existing components make sense.




            How are my Vue skills?




            Usage of Vue looks good. There are a few general JS aspects that I have feedback for (see below, under last "question") but usage of Vue components and other constructs looks good.




            Can anything be done better with regards to how I am using Vue?




            Bearing in mind I am not an expert VueJS user and have only been working with it on small projects in the past year, I can't really think of anything... If you really wanted you could consider using slots somehow, or an Event bus if the components became more separated but that might not be nessary since everything is contained in the main RoyalGameOfUR component.



            If I think of anything else, I will surely update this answer.




            I am nowhere near a UX-designer, but how is the user experience?




            The layout of the game components is okay, though it would be helpful to have more text prompting the user what to do, or at least the rules and game play instructions somewhere (e.g. in a text box, linked to another page, etc.). In the same vain, I see an uncaught exception in the console if the user clicks the dice when it isn't time to roll. One could catch the exception and alert the user about what happened.




            Any other feedback also welcome.




            Feedback



            Wow that is a really elegant application! Well done! I haven't used the grid styles yet but hope to in the future.



            I did notice that after rolling the dice, when selecting a piece from a stack, it doesn't matter which player is the current player - I can click on either stack (though only a piece from the current player's stack will get moved).



            I did notice an error once about this.onclick is not defined but I didn't observe the path to reproduce it. If I see it again I will let you know.



            Suggestions



            JS




            let & const



            I see the code utilizes let in a few places but otherwise just var. It would be wise to start using const anywhere a value is stored but never re-assigned - then use let if re-assignment is necessary. Using var outside of a function declares a global variable1...I only spot one of those in your post (i.e. var games) but if there were other places where you wanted a variable in another file called games then this could lead to unintentional value over-writing.



            Array copying



            In piecesToObjects(), I see these lines:




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



            You could utilize Array.from() to copy the array, then use array.map() to call mapping() instead of using the for loop. Originally I was thinking that the forEach could be eliminated but there is a need to get a regular array instead of the typed array (i.e. Int32Array). If the array being copied (i.e. array) was a regular array, then you likely could just use .map() - see this jsPerf to see how much quicker that mapping could be.



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


            And that function mapping could be pulled out of piecesToObjects if playerIndex is accepted as the first parameter, and then playerIndex can be sent on each iteration using Function.bind() - i.e. using a partially applied function.



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


            Nested Ternary operator



            Bearing in mind that this might just be maintained by you, if somebody else wanted to update the code, that person might find the line below less readable than several normal if blocks. My former supervisor had a rule: no more than one ternary operator in one expression - especially if it made the line longer than ~100 characters.




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



            Something a little more readable might be:



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


            0-based Flower grid areas



            Why add 1 to the x and y values in UrFlower's template? Perhaps you are so used to 0-based indexes and wanted to keep those values in the markup orthogonal with your ways... Those flowers could be put unto an array and looped over using v-for... but for 5 flowers that might be too excessive...



            CSS



            Inline style vs CSS



            There are static inline style attributes in UrPlayerView.vue - e.g. :




            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;"



            and




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



            The position and top styles could be put into the existing ruleset for .piece-small...



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







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited May 14 at 21:34

























            answered Apr 5 at 2:23









            Sᴀᴍ Onᴇᴌᴀ

            7,88561750




            7,88561750








            • 1




              I think the Vue <style scoped> is not the same as a HTML 5 <style scoped>. Vue adds some data-things to only apply the style to things from the same component. vue-loader.vuejs.org/en/features/scoped-css.html
              – Simon Forsberg
              Apr 6 at 8:28












            • Ah - my colleague setup the vue loader config before I started working on the project and I must have skipped over that section when learning about vue loader. Good correction!
              – Sᴀᴍ Onᴇᴌᴀ
              Apr 6 at 18:24














            • 1




              I think the Vue <style scoped> is not the same as a HTML 5 <style scoped>. Vue adds some data-things to only apply the style to things from the same component. vue-loader.vuejs.org/en/features/scoped-css.html
              – Simon Forsberg
              Apr 6 at 8:28












            • Ah - my colleague setup the vue loader config before I started working on the project and I must have skipped over that section when learning about vue loader. Good correction!
              – Sᴀᴍ Onᴇᴌᴀ
              Apr 6 at 18:24








            1




            1




            I think the Vue <style scoped> is not the same as a HTML 5 <style scoped>. Vue adds some data-things to only apply the style to things from the same component. vue-loader.vuejs.org/en/features/scoped-css.html
            – Simon Forsberg
            Apr 6 at 8:28






            I think the Vue <style scoped> is not the same as a HTML 5 <style scoped>. Vue adds some data-things to only apply the style to things from the same component. vue-loader.vuejs.org/en/features/scoped-css.html
            – Simon Forsberg
            Apr 6 at 8:28














            Ah - my colleague setup the vue loader config before I started working on the project and I must have skipped over that section when learning about vue loader. Good correction!
            – Sᴀᴍ Onᴇᴌᴀ
            Apr 6 at 18:24




            Ah - my colleague setup the vue loader config before I started working on the project and I must have skipped over that section when learning about vue loader. Good correction!
            – Sᴀᴍ Onᴇᴌᴀ
            Apr 6 at 18:24












            up vote
            1
            down vote













            [1]



            I have become more acquainted with ecmascript-6 since I gave the answer back in April, and realize now that because you are using ecmascript-6 features like const, let and arrow functions, other es-6 features could be used as well.



            For instance, the following lines in the shuffle() function:




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



            Could be simplified to a single line using (Array) destructuring assignment



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


            I also suggested using Array.from() to copy the array of pieces in piecesToObjects(), and while that is part of the es-6 standard, the spread syntax could be used instead of calling that function. Instead of a final statement like




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



            You should be able to use that spread syntax:



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





            share|improve this answer

























              up vote
              1
              down vote













              [1]



              I have become more acquainted with ecmascript-6 since I gave the answer back in April, and realize now that because you are using ecmascript-6 features like const, let and arrow functions, other es-6 features could be used as well.



              For instance, the following lines in the shuffle() function:




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



              Could be simplified to a single line using (Array) destructuring assignment



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


              I also suggested using Array.from() to copy the array of pieces in piecesToObjects(), and while that is part of the es-6 standard, the spread syntax could be used instead of calling that function. Instead of a final statement like




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



              You should be able to use that spread syntax:



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





              share|improve this answer























                up vote
                1
                down vote










                up vote
                1
                down vote









                [1]



                I have become more acquainted with ecmascript-6 since I gave the answer back in April, and realize now that because you are using ecmascript-6 features like const, let and arrow functions, other es-6 features could be used as well.



                For instance, the following lines in the shuffle() function:




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



                Could be simplified to a single line using (Array) destructuring assignment



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


                I also suggested using Array.from() to copy the array of pieces in piecesToObjects(), and while that is part of the es-6 standard, the spread syntax could be used instead of calling that function. Instead of a final statement like




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



                You should be able to use that spread syntax:



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





                share|improve this answer












                [1]



                I have become more acquainted with ecmascript-6 since I gave the answer back in April, and realize now that because you are using ecmascript-6 features like const, let and arrow functions, other es-6 features could be used as well.



                For instance, the following lines in the shuffle() function:




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



                Could be simplified to a single line using (Array) destructuring assignment



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


                I also suggested using Array.from() to copy the array of pieces in piecesToObjects(), and while that is part of the es-6 standard, the spread syntax could be used instead of calling that function. Instead of a final statement like




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



                You should be able to use that spread syntax:



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






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Nov 25 at 6:28









                Sᴀᴍ Onᴇᴌᴀ

                7,88561750




                7,88561750






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Code Review Stack Exchange!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    Use MathJax to format equations. MathJax reference.


                    To learn more, see our tips on writing great answers.





                    Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                    Please pay close attention to the following guidance:


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190896%2fvue-its-the-royal-game-of-ur%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Список кардиналов, возведённых папой римским Каликстом III

                    Deduzione

                    Mysql.sock missing - “Can't connect to local MySQL server through socket”