Implement surrender of reversi
This commit is contained in:
parent
244d567b3a
commit
be9f6ad294
|
@ -182,6 +182,10 @@ common/views/components/games/reversi/reversi.vue:
|
||||||
waiting-for: "{}を待っています"
|
waiting-for: "{}を待っています"
|
||||||
cancel: "キャンセル"
|
cancel: "キャンセル"
|
||||||
|
|
||||||
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
|
surrender: "投了"
|
||||||
|
surrendered: "投了により"
|
||||||
|
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="xqnhankfuuilcwvhgsopeqncafzsquya">
|
<div class="xqnhankfuuilcwvhgsopeqncafzsquya">
|
||||||
<header><b>{{ blackUser | userName }}</b>(%i18n:common.reversi.black%) vs <b>{{ whiteUser | userName }}</b>(%i18n:common.reversi.white%)</header>
|
<header><b><router-link :to="blackUser | userPage">{{ blackUser | userName }}</router-link></b>(%i18n:common.reversi.black%) vs <b><router-link :to="whiteUser | userPage">{{ whiteUser | userName }}</router-link></b>(%i18n:common.reversi.white%)</header>
|
||||||
|
|
||||||
<div style="overflow: hidden">
|
<div style="overflow: hidden">
|
||||||
<p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ '%i18n:common.reversi.turn-of%'.replace('{}', $options.filters.userName(turnUser)) }}<mk-ellipsis/></p>
|
<p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ '%i18n:common.reversi.turn-of%'.replace('{}', $options.filters.userName(turnUser)) }}<mk-ellipsis/></p>
|
||||||
|
@ -8,7 +8,10 @@
|
||||||
<p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">%i18n:common.reversi.opponent-turn%<mk-ellipsis/></p>
|
<p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">%i18n:common.reversi.opponent-turn%<mk-ellipsis/></p>
|
||||||
<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">%i18n:common.reversi.my-turn%</p>
|
<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">%i18n:common.reversi.my-turn%</p>
|
||||||
<p class="result" v-if="game.isEnded && logPos == logs.length">
|
<p class="result" v-if="game.isEnded && logPos == logs.length">
|
||||||
<template v-if="game.winner">{{ '%i18n:common.reversi.won%'.replace('{}', $options.filters.userName(game.winner)) }}{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
|
<template v-if="game.winner">
|
||||||
|
<span>{{ '%i18n:common.reversi.won%'.replace('{}', $options.filters.userName(game.winner)) }}</span>
|
||||||
|
<span v-if="game.surrendered != null"> (%i18n:@surrendered%)</span>
|
||||||
|
</template>
|
||||||
<template v-else>%i18n:common.reversi.drawn%</template>
|
<template v-else>%i18n:common.reversi.drawn%</template>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,6 +44,10 @@
|
||||||
|
|
||||||
<p class="status"><b>{{ '%i18n:common.reversi.this-turn%'.split('{}')[0] }}{{ logPos }}{{ '%i18n:common.reversi.this-turn%'.split('{}')[1] }}</b> %i18n:common.reversi.black%:{{ o.blackCount }} %i18n:common.reversi.white%:{{ o.whiteCount }} %i18n:common.reversi.total%:{{ o.blackCount + o.whiteCount }}</p>
|
<p class="status"><b>{{ '%i18n:common.reversi.this-turn%'.split('{}')[0] }}{{ logPos }}{{ '%i18n:common.reversi.this-turn%'.split('{}')[1] }}</b> %i18n:common.reversi.black%:{{ o.blackCount }} %i18n:common.reversi.white%:{{ o.whiteCount }} %i18n:common.reversi.total%:{{ o.blackCount + o.whiteCount }}</p>
|
||||||
|
|
||||||
|
<div class="actions" v-if="!game.isEnded && iAmPlayer">
|
||||||
|
<form-button @click="surrender">%i18n:@surrender%</form-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="player" v-if="game.isEnded">
|
<div class="player" v-if="game.isEnded">
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</el-button>
|
<el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</el-button>
|
||||||
|
@ -79,22 +86,27 @@ export default Vue.extend({
|
||||||
if (!this.$store.getters.isSignedIn) return false;
|
if (!this.$store.getters.isSignedIn) return false;
|
||||||
return this.game.user1Id == this.$store.state.i.id || this.game.user2Id == this.$store.state.i.id;
|
return this.game.user1Id == this.$store.state.i.id || this.game.user2Id == this.$store.state.i.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
myColor(): Color {
|
myColor(): Color {
|
||||||
if (!this.iAmPlayer) return null;
|
if (!this.iAmPlayer) return null;
|
||||||
if (this.game.user1Id == this.$store.state.i.id && this.game.black == 1) return true;
|
if (this.game.user1Id == this.$store.state.i.id && this.game.black == 1) return true;
|
||||||
if (this.game.user2Id == this.$store.state.i.id && this.game.black == 2) return true;
|
if (this.game.user2Id == this.$store.state.i.id && this.game.black == 2) return true;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
opColor(): Color {
|
opColor(): Color {
|
||||||
if (!this.iAmPlayer) return null;
|
if (!this.iAmPlayer) return null;
|
||||||
return this.myColor === true ? false : true;
|
return this.myColor === true ? false : true;
|
||||||
},
|
},
|
||||||
|
|
||||||
blackUser(): any {
|
blackUser(): any {
|
||||||
return this.game.black == 1 ? this.game.user1 : this.game.user2;
|
return this.game.black == 1 ? this.game.user1 : this.game.user2;
|
||||||
},
|
},
|
||||||
|
|
||||||
whiteUser(): any {
|
whiteUser(): any {
|
||||||
return this.game.black == 1 ? this.game.user2 : this.game.user1;
|
return this.game.black == 1 ? this.game.user2 : this.game.user1;
|
||||||
},
|
},
|
||||||
|
|
||||||
turnUser(): any {
|
turnUser(): any {
|
||||||
if (this.o.turn === true) {
|
if (this.o.turn === true) {
|
||||||
return this.game.black == 1 ? this.game.user1 : this.game.user2;
|
return this.game.black == 1 ? this.game.user1 : this.game.user2;
|
||||||
|
@ -104,11 +116,13 @@ export default Vue.extend({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
isMyTurn(): boolean {
|
isMyTurn(): boolean {
|
||||||
if (!this.iAmPlayer) return false;
|
if (!this.iAmPlayer) return false;
|
||||||
if (this.turnUser == null) return false;
|
if (this.turnUser == null) return false;
|
||||||
return this.turnUser.id == this.$store.state.i.id;
|
return this.turnUser.id == this.$store.state.i.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
cellsStyle(): any {
|
cellsStyle(): any {
|
||||||
return {
|
return {
|
||||||
'grid-template-rows': `repeat(${this.game.settings.map.length}, 1fr)`,
|
'grid-template-rows': `repeat(${this.game.settings.map.length}, 1fr)`,
|
||||||
|
@ -165,11 +179,13 @@ export default Vue.extend({
|
||||||
mounted() {
|
mounted() {
|
||||||
this.connection.on('set', this.onSet);
|
this.connection.on('set', this.onSet);
|
||||||
this.connection.on('rescue', this.onRescue);
|
this.connection.on('rescue', this.onRescue);
|
||||||
|
this.connection.on('ended', this.onEnded);
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.off('set', this.onSet);
|
this.connection.off('set', this.onSet);
|
||||||
this.connection.off('rescue', this.onRescue);
|
this.connection.off('rescue', this.onRescue);
|
||||||
|
this.connection.off('ended', this.onEnded);
|
||||||
|
|
||||||
clearInterval(this.pollingClock);
|
clearInterval(this.pollingClock);
|
||||||
},
|
},
|
||||||
|
@ -215,6 +231,10 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onEnded(x) {
|
||||||
|
this.game = x.game;
|
||||||
|
},
|
||||||
|
|
||||||
checkEnd() {
|
checkEnd() {
|
||||||
this.game.isEnded = this.o.isEnded;
|
this.game.isEnded = this.o.isEnded;
|
||||||
if (this.game.isEnded) {
|
if (this.game.isEnded) {
|
||||||
|
@ -250,6 +270,12 @@ export default Vue.extend({
|
||||||
|
|
||||||
this.checkEnd();
|
this.checkEnd();
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
surrender() {
|
||||||
|
(this as any).api('games/reversi/games/surrender', {
|
||||||
|
gameId: this.game.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -265,6 +291,9 @@ root(isDark)
|
||||||
padding 8px
|
padding 8px
|
||||||
border-bottom dashed 1px isDark ? #4c5761 : #c4cdd4
|
border-bottom dashed 1px isDark ? #4c5761 : #c4cdd4
|
||||||
|
|
||||||
|
a
|
||||||
|
color inherit
|
||||||
|
|
||||||
> .board
|
> .board
|
||||||
width calc(100% - 16px)
|
width calc(100% - 16px)
|
||||||
max-width 500px
|
max-width 500px
|
||||||
|
@ -381,6 +410,9 @@ root(isDark)
|
||||||
margin 0
|
margin 0
|
||||||
padding 16px 0
|
padding 16px 0
|
||||||
|
|
||||||
|
> .actions
|
||||||
|
padding-bottom 16px
|
||||||
|
|
||||||
> .player
|
> .player
|
||||||
padding-bottom 32px
|
padding-bottom 32px
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ export interface IReversiGame {
|
||||||
isStarted: boolean;
|
isStarted: boolean;
|
||||||
isEnded: boolean;
|
isEnded: boolean;
|
||||||
winnerId: mongo.ObjectID;
|
winnerId: mongo.ObjectID;
|
||||||
|
surrendered: mongo.ObjectID;
|
||||||
logs: Array<{
|
logs: Array<{
|
||||||
at: Date;
|
at: Date;
|
||||||
color: boolean;
|
color: boolean;
|
||||||
|
|
59
src/server/api/endpoints/games/reversi/games/surrender.ts
Normal file
59
src/server/api/endpoints/games/reversi/games/surrender.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import $ from 'cafy'; import ID from '../../../../../../misc/cafy-id';
|
||||||
|
import ReversiGame, { pack } from '../../../../../../models/games/reversi/game';
|
||||||
|
import { ILocalUser } from '../../../../../../models/user';
|
||||||
|
import getParams from '../../../../get-params';
|
||||||
|
import { publishReversiGameStream } from '../../../../../../stream';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
ja: '指定したリバーシの対局で投了します。'
|
||||||
|
},
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
params: {
|
||||||
|
gameId: $.type(ID).optional.note({
|
||||||
|
desc: {
|
||||||
|
ja: '投了したい対局'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
|
||||||
|
const [ps, psErr] = getParams(meta, params);
|
||||||
|
if (psErr) return rej(psErr);
|
||||||
|
|
||||||
|
const game = await ReversiGame.findOne({ _id: ps.gameId });
|
||||||
|
|
||||||
|
if (game == null) {
|
||||||
|
return rej('game not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.isEnded) {
|
||||||
|
return rej('this game is already ended');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!game.user1Id.equals(user._id) && !game.user2Id.equals(user._id)) {
|
||||||
|
return rej('access denied');
|
||||||
|
}
|
||||||
|
|
||||||
|
const winnerId = game.user1Id.equals(user._id) ? game.user2Id : game.user1Id;
|
||||||
|
|
||||||
|
await ReversiGame.update({
|
||||||
|
_id: game._id
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
surrendered: user._id,
|
||||||
|
isEnded: true,
|
||||||
|
winnerId: winnerId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
publishReversiGameStream(game._id, 'ended', {
|
||||||
|
winnerId: winnerId,
|
||||||
|
game: await pack(game._id, user)
|
||||||
|
});
|
||||||
|
|
||||||
|
res();
|
||||||
|
});
|
Loading…
Reference in a new issue