Implement game rules

This commit is contained in:
Tim 2021-12-02 02:43:32 +01:00
parent 5a39307fa6
commit ffded017c7
9 changed files with 213 additions and 99 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<img width="50px" :src="'Dice-' + number + '-b.svg'" alt="dice"> <img width="50px" :src="'Dice-' + number + '-b.svg'" alt="dice" :class="{ inverted: selected }">
</template> </template>
<script lang="ts"> <script lang="ts">
@ -8,5 +8,14 @@ import {Component, Prop, Vue} from 'vue-property-decorator'
@Component @Component
export default class Die extends Vue { export default class Die extends Vue {
@Prop() private number!: number; @Prop() private number!: number;
@Prop() private selected!: boolean;
} }
</script> </script>
<style scoped lang="scss">
.inverted {
// https://codepen.io/sosuke/pen/Pjoqqp
filter: invert(58%) sepia(99%) saturate(4021%) hue-rotate(203deg) brightness(103%) contrast(100%);
//filter: invert(43%) sepia(31%) saturate(2346%) hue-rotate(324deg) brightness(109%) contrast(113%);
}
</style>

View File

@ -0,0 +1,127 @@
<template>
<form>
Rules:<br/>
<md-field>
<label>Start dice count per player</label>
<md-input type="number" v-model="startDicePerPlayer" :readonly="readonly"></md-input>
</md-field>
<md-checkbox v-model="higherCountLowerValueEnabled" :disabled="readonly">
Higher count lower value
</md-checkbox>
<br/>
<md-checkbox v-model="jokerEnabled" :disabled="readonly">Joker enabled</md-checkbox>
<br/>
<md-checkbox v-model="jokerCountsDouble" :disabled="readonly">Joker counts double</md-checkbox>
<br/>
Joker die value: <br>
<span v-for="n in 6" @click="!readonly && (jokerDieValue = n)">
<die :number="n" :selected="jokerDieValue === n"></die>
</span> <br>
<md-checkbox v-model="palificoEnabled" :disabled="readonly">Palifico enabled</md-checkbox>
<br/>
<md-checkbox v-model="calzaEnabled" :disabled="readonly">Calza enabled</md-checkbox>
<br/>
<md-checkbox v-model="randomPlayerOrder" :disabled="readonly">Random player order</md-checkbox>
<br/>
<md-checkbox v-model="anonymizePlayers" :disabled="readonly">Anonymize players</md-checkbox>
</form>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import { GameRules } from "@/objects/objects";
import Die from "@/components/Die.vue";
@Component({
components: {Die}
})
export default class GameRulesEditor extends Vue {
@Prop() private value!: GameRules;
@Prop() private readonly!: boolean;
private get startDicePerPlayer (): number {
return this.value.startDicePerPlayer;
}
private set startDicePerPlayer (b: number) {
this.value.startDicePerPlayer = b;
this.emitRules();
}
private get jokerDieValue (): number {
return this.value.jokerDieValue;
}
private set jokerDieValue (b: number) {
this.value.jokerDieValue = b;
this.emitRules();
}
private get higherCountLowerValueEnabled (): boolean {
return this.value.higherCountLowerValueEnabled;
}
private set higherCountLowerValueEnabled (b: boolean) {
this.value.higherCountLowerValueEnabled = b;
this.emitRules();
}
private get jokerEnabled (): boolean {
return this.value.jokerEnabled;
}
private set jokerEnabled (b: boolean) {
this.value.jokerEnabled = b;
this.emitRules();
}
private get jokerCountsDouble (): boolean {
return this.value.jokerCountsDouble;
}
private set jokerCountsDouble (b: boolean) {
this.value.jokerCountsDouble = b;
this.emitRules();
}
private get palificoEnabled (): boolean {
return this.value.palificoEnabled;
}
private set palificoEnabled (b: boolean) {
this.value.palificoEnabled = b;
this.emitRules();
}
private get calzaEnabled (): boolean {
return this.value.calzaEnabled;
}
private set calzaEnabled (b: boolean) {
this.value.calzaEnabled = b;
this.emitRules();
}
private get randomPlayerOrder (): boolean {
return this.value.randomPlayerOrder;
}
private set randomPlayerOrder (b: boolean) {
this.value.randomPlayerOrder = b;
this.emitRules();
}
private get anonymizePlayers (): boolean {
return this.value.anonymizePlayers;
}
private set anonymizePlayers (b: boolean) {
this.value.anonymizePlayers = b;
this.emitRules();
}
private emitRules (): void {
this.$emit('input', this.value);
}
}
</script>

View File

@ -1,65 +0,0 @@
<template>
<div class="hello center">
<img alt="Vue logo" src="../assets/logo.png">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.center {
text-align: center;
}
</style>

View File

@ -11,7 +11,7 @@
<td> <td>
<die v-for="n in turn.diceCount" :number="turn.dieValue" :key="'g'+n+turn.dieValue"></die> <die v-for="n in turn.diceCount" :number="turn.dieValue" :key="'g'+n+turn.dieValue"></die>
</td> </td>
</tr> </tr>x
</table> </table>
</template> </template>

View File

@ -38,6 +38,19 @@ export interface GameStateObject {
players: PlayerObject[], players: PlayerObject[],
currentPlayer: PlayerObject, currentPlayer: PlayerObject,
owningPlayer: PlayerObject, owningPlayer: PlayerObject,
rules: GameRules
}
export interface GameRules {
startDicePerPlayer: number,
higherCountLowerValueEnabled: boolean,
jokerCountsDouble: boolean,
jokerEnabled: boolean,
palificoEnabled: boolean,
calzaEnabled: boolean,
jokerDieValue: number,
randomPlayerOrder: boolean,
anonymizePlayers: boolean,
} }
export interface RoundObject { export interface RoundObject {

View File

@ -5,6 +5,7 @@ import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default-dark.css' import 'vue-material/dist/theme/default-dark.css'
import MdButton from 'vue-material/dist/components/MdButton'; import MdButton from 'vue-material/dist/components/MdButton';
import MdCheckbox from 'vue-material/dist/components/MdCheckbox';
import MdField from 'vue-material/dist/components/MdField'; import MdField from 'vue-material/dist/components/MdField';
import MdApp from 'vue-material/dist/components/MdApp'; import MdApp from 'vue-material/dist/components/MdApp';
import MdAppContent from 'vue-material/dist/components/MdContent'; import MdAppContent from 'vue-material/dist/components/MdContent';
@ -12,6 +13,7 @@ Vue.use(MdButton);
Vue.use(MdField); Vue.use(MdField);
Vue.use(MdApp); Vue.use(MdApp);
Vue.use(MdAppContent); Vue.use(MdAppContent);
Vue.use(MdCheckbox)
Vue.use(VueRouter) Vue.use(VueRouter)

View File

@ -1,5 +1,13 @@
import axios, {AxiosPromise, AxiosResponse} from 'axios' import axios, {AxiosPromise, AxiosResponse} from 'axios'
import {JoinCreateGameObject, IsStartedObject, ApiObject, GameStateObject, MyTurnObject, GuessAction} from "@/objects/objects"; import {
JoinCreateGameObject,
IsStartedObject,
ApiObject,
GameStateObject,
MyTurnObject,
GuessAction,
GameRules
} from "@/objects/objects";
export default class PerudoApi { export default class PerudoApi {
private static baseUrl = process.env.VUE_APP_BASE_URL; private static baseUrl = process.env.VUE_APP_BASE_URL;
@ -27,6 +35,13 @@ export default class PerudoApi {
}) })
} }
public setGameRules(playerId: string, rules: GameRules): Promise<ApiObject> {
return this.post<ApiObject>('game/rules/' + playerId, rules)
.then((response: AxiosResponse<ApiObject>) => {
return response.data;
});
}
public gameStarted(playerId: string): Promise<IsStartedObject> { public gameStarted(playerId: string): Promise<IsStartedObject> {
return this.get<IsStartedObject>('game/started/' + playerId) return this.get<IsStartedObject>('game/started/' + playerId)
.then((response: AxiosResponse<IsStartedObject>) => { .then((response: AxiosResponse<IsStartedObject>) => {

View File

@ -1,5 +1,6 @@
declare module 'vue-material'; declare module 'vue-material';
declare module 'vue-material/dist/components/MdButton'; declare module 'vue-material/dist/components/MdButton';
declare module 'vue-material/dist/components/MdCheckbox';
declare module 'vue-material/dist/components/MdField'; declare module 'vue-material/dist/components/MdField';
declare module 'vue-material/dist/components/MdApp'; declare module 'vue-material/dist/components/MdApp';
declare module 'vue-material/dist/components/MdContent'; declare module 'vue-material/dist/components/MdContent';

View File

@ -39,8 +39,8 @@
<md-input type="number" v-model="diceCount"></md-input> <md-input type="number" v-model="diceCount"></md-input>
</md-field> </md-field>
Die value: <br> Die value: <br>
<span v-for="n in 6" :key="'v'+n" @click="dieValue = n" :class="(dieValue === n) ? 'inverted' : ''"> <span v-for="n in 6" :key="'v'+n" @click="dieValue = n">
<die :number="n"></die> <die :number="n" :selected="dieValue === n"></die>
</span> <br><br> </span> <br><br>
<md-button @click="makeGuess" class="md-raised md-primary">Guess</md-button> <md-button @click="makeGuess" class="md-raised md-primary">Guess</md-button>
<md-button @click="callBluff" class="md-raised md-accent">Call</md-button> <md-button @click="callBluff" class="md-raised md-accent">Call</md-button>
@ -62,6 +62,8 @@
<template v-for="player in gameStateObject.players"> <template v-for="player in gameStateObject.players">
- {{ player.name }}<br> - {{ player.name }}<br>
</template> </template>
<br/>
<game-rules-editor v-model="rules" :readonly="!owner"/>
</template> </template>
</template> </template>
@ -70,7 +72,8 @@
</template> </template>
<template v-if="gameStateObject && gameRunning"> <template v-if="gameStateObject && gameRunning">
<span v-if="previousRound && previousRound.loser.hash === playerHash">You lost the previous round!<br></span> <span
v-if="previousRound && previousRound.loser.hash === playerHash">You lost the previous round!<br></span>
Round: {{ currentRound.number + 1 }} <br> Round: {{ currentRound.number + 1 }} <br>
Turn: {{ lastTurn ? lastTurn.number + 2 : 1 }} <br> Turn: {{ lastTurn ? lastTurn.number + 2 : 1 }} <br>
@ -81,7 +84,7 @@
<br><br> <br><br>
<template v-if="lastTurn"> <template v-if="lastTurn">
Last guesses: <br> Last guesses: <br>
<round-turns :round="currentRound" /> <round-turns :round="currentRound"/>
</template> </template>
<br> <br>
<template v-if="!myTurn">Someone else is playing, waiting for your turn<br></template> <template v-if="!myTurn">Someone else is playing, waiting for your turn<br></template>
@ -111,7 +114,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {Component, Vue} from 'vue-property-decorator' import { Component, Vue } from 'vue-property-decorator'
import PerudoApi from "@/services/PerudoApi"; import PerudoApi from "@/services/PerudoApi";
import Die from "@/components/Die.vue"; import Die from "@/components/Die.vue";
import { import {
@ -124,13 +127,15 @@ import {
RoundObject, RoundObject,
TurnObject, TurnObject,
GameStateState, GameStateState,
PlayerObject PlayerObject,
GameRules
} from "@/objects/objects"; } from "@/objects/objects";
import Round from "@/components/Round.vue"; import Round from "@/components/Round.vue";
import RoundTurns from "@/components/RoundTurns.vue"; import RoundTurns from "@/components/RoundTurns.vue";
import GameRulesEditor from "@/components/GameRulesEditor.vue";
@Component({ @Component({
components: {RoundTurns, Round, Die} components: {GameRulesEditor, RoundTurns, Round, Die}
}) })
export default class Home extends Vue { export default class Home extends Vue {
private gameState: GameState = GameState.Setup; private gameState: GameState = GameState.Setup;
@ -148,27 +153,27 @@ export default class Home extends Vue {
private diceCount: string = '1'; private diceCount: string = '1';
private dieValue: number = 2; private dieValue: number = 2;
private get gameJoined(): boolean { private get gameJoined (): boolean {
return this.gameState >= GameState.Joined; return this.gameState >= GameState.Joined;
} }
private get gameStarted(): boolean { private get gameStarted (): boolean {
return this.gameState >= GameState.Started; return this.gameState >= GameState.Started;
} }
private get gameRunning(): boolean { private get gameRunning (): boolean {
return this.gameStarted && !this.gameEnded; return this.gameStarted && !this.gameEnded;
} }
private get gameEnded(): boolean { private get gameEnded (): boolean {
return this.gameState === GameState.Ended; return this.gameState === GameState.Ended;
} }
private get myTurn(): boolean { private get myTurn (): boolean {
return this.gameState === GameState.MyTurn; return this.gameState === GameState.MyTurn;
} }
private get playersKeyedById(): Record<string, PlayerObject> | undefined { private get playersKeyedById (): Record<string, PlayerObject> | undefined {
let newPlayers: Record<string, PlayerObject> = {}; let newPlayers: Record<string, PlayerObject> = {};
if (this.gameStateObject) { if (this.gameStateObject) {
let players = this.gameStateObject.players; let players = this.gameStateObject.players;
@ -179,22 +184,33 @@ export default class Home extends Vue {
return newPlayers; return newPlayers;
} }
private get previousRound(): RoundObject | undefined { private get previousRound (): RoundObject | undefined {
let rounds = this.gameStateObject?.rounds; let rounds = this.gameStateObject?.rounds;
return rounds ? (rounds[rounds.length - 2] ?? undefined) : undefined; return rounds ? (rounds[rounds.length - 2] ?? undefined) : undefined;
} }
private get currentRound(): RoundObject | undefined { private get currentRound (): RoundObject | undefined {
let rounds = this.gameStateObject?.rounds; let rounds = this.gameStateObject?.rounds;
return rounds ? rounds[rounds.length - 1] : undefined; return rounds ? rounds[rounds.length - 1] : undefined;
} }
private get lastTurn(): TurnObject | undefined { private get lastTurn (): TurnObject | undefined {
let turns = this.currentRound?.turns; let turns = this.currentRound?.turns;
return turns ? turns[turns.length - 1] : undefined; return turns ? turns[turns.length - 1] : undefined;
} }
private createGame(): void { private get rules (): GameRules | undefined {
return this.gameStateObject?.rules;
}
private set rules (gameRules: GameRules | undefined) {
console.log(gameRules);
if (this.playerId && gameRules) {
PerudoApi.instance.setGameRules(this.playerId, gameRules);
}
}
private createGame (): void {
if (this.name === '') { if (this.name === '') {
this.error = "Name cannot be empty"; this.error = "Name cannot be empty";
return; return;
@ -206,7 +222,7 @@ export default class Home extends Vue {
this.owner = true; this.owner = true;
this.gameState = GameState.Joined; this.gameState = GameState.Joined;
this.gameTimer = setInterval(() => { setTimeout(() => {
this.checkStarted(); this.checkStarted();
}, 1000); }, 1000);
}).catch((reason => { }).catch((reason => {
@ -214,7 +230,7 @@ export default class Home extends Vue {
})); }));
} }
private joinGame(): void { private joinGame (): void {
if (this.code === '') { if (this.code === '') {
this.error = "Game code cannot be empty when joining a game"; this.error = "Game code cannot be empty when joining a game";
return; return;
@ -236,18 +252,19 @@ export default class Home extends Vue {
})); }));
} }
private startGame(): void { private startGame (): void {
if (this.gameJoined && this.playerId) { if (this.gameJoined && this.playerId) {
PerudoApi.instance.startGame(this.playerId).then((response: ApiObject) => { PerudoApi.instance.startGame(this.playerId).then((response: ApiObject) => {
this.gameState = GameState.Started; this.gameState = GameState.Started;
this.gameStateObject = null; // Bug fix because checkStarted gameStateObject misses some properties this.gameStateObject = null; // Bug fix because checkStarted gameStateObject misses some properties
this.checkTurn();
}).catch((reason => { }).catch((reason => {
this.error = reason; this.error = reason;
})); }));
} }
} }
private makeGuess(): void { private makeGuess (): void {
if (this.myTurn && this.playerId) { if (this.myTurn && this.playerId) {
PerudoApi.instance.makeGuess(this.playerId, { PerudoApi.instance.makeGuess(this.playerId, {
diceCount: parseInt(this.diceCount), diceCount: parseInt(this.diceCount),
@ -264,7 +281,7 @@ export default class Home extends Vue {
} }
} }
private callBluff(): void { private callBluff (): void {
if (this.myTurn && this.playerId) { if (this.myTurn && this.playerId) {
PerudoApi.instance.callBluff(this.playerId).then((response: MyTurnObject) => { PerudoApi.instance.callBluff(this.playerId).then((response: MyTurnObject) => {
this.gameState = GameState.Started; this.gameState = GameState.Started;
@ -278,11 +295,12 @@ export default class Home extends Vue {
} }
} }
private checkStarted(): void { private checkStarted (): void {
console.log("Check started"); console.log("Check started");
if (this.gameJoined && this.playerId) { if (this.gameJoined && this.playerId) {
PerudoApi.instance.gameStarted(this.playerId).then((response: IsStartedObject) => { PerudoApi.instance.gameStarted(this.playerId).then((response: IsStartedObject) => {
this.gameStateObject = response.gameState; this.gameStateObject = response.gameState;
console.log(this.gameStateObject.rules);
if (response.started) { if (response.started) {
this.gameState = GameState.Started; this.gameState = GameState.Started;
@ -297,7 +315,7 @@ export default class Home extends Vue {
} }
} }
private checkTurn(): void { private checkTurn (): void {
console.log("Check turn"); console.log("Check turn");
if (this.gameStarted && this.playerId) { if (this.gameStarted && this.playerId) {
PerudoApi.instance.myTurn(this.playerId).then((response: MyTurnObject) => { PerudoApi.instance.myTurn(this.playerId).then((response: MyTurnObject) => {
@ -318,7 +336,7 @@ export default class Home extends Vue {
} }
} }
private clearGameTimer(): void { private clearGameTimer (): void {
if (this.gameTimer) { if (this.gameTimer) {
clearInterval(this.gameTimer); clearInterval(this.gameTimer);
this.gameTimer = null; this.gameTimer = null;
@ -326,9 +344,3 @@ export default class Home extends Vue {
} }
} }
</script> </script>
<style scoped lang="scss">
.inverted {
filter: invert(1);
}
</style>