From 98f09ad16c8dbdfedfb1a517203f84e584fb0bee Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Sun, 11 Mar 2018 07:07:17 +0900 Subject: [PATCH] :v: --- src/api/models/othello-game.ts | 2 + src/api/stream/othello-game.ts | 76 ++++++ .../common/views/components/othello.room.vue | 230 +++++++++++++----- 3 files changed, 253 insertions(+), 55 deletions(-) diff --git a/src/api/models/othello-game.ts b/src/api/models/othello-game.ts index a8c3025108..ab90cffa44 100644 --- a/src/api/models/othello-game.ts +++ b/src/api/models/othello-game.ts @@ -33,6 +33,8 @@ export interface IGame { can_put_everywhere: boolean; looped_board: boolean; }; + form1: any; + form2: any; } /** diff --git a/src/api/stream/othello-game.ts b/src/api/stream/othello-game.ts index 5f61f0cc2c..888c599338 100644 --- a/src/api/stream/othello-game.ts +++ b/src/api/stream/othello-game.ts @@ -31,6 +31,21 @@ export default function(request: websocket.request, connection: websocket.connec updateSettings(msg.settings); break; + case 'init-form': + if (msg.body == null) return; + initForm(msg.body); + break; + + case 'update-form': + if (msg.id == null || msg.value === undefined) return; + updateForm(msg.id, msg.value); + break; + + case 'message': + if (msg.body == null) return; + message(msg.body); + break; + case 'set': if (msg.pos == null) return; set(msg.pos); @@ -55,6 +70,67 @@ export default function(request: websocket.request, connection: websocket.connec publishOthelloGameStream(gameId, 'update-settings', settings); } + async function initForm(form) { + const game = await Game.findOne({ _id: gameId }); + + if (game.is_started) return; + if (!game.user1_id.equals(user._id) && !game.user2_id.equals(user._id)) return; + + const set = game.user1_id.equals(user._id) ? { + form1: form + } : { + form2: form + }; + + await Game.update({ _id: gameId }, { + $set: set + }); + + publishOthelloGameStream(gameId, 'init-form', { + user_id: user._id, + form + }); + } + + async function updateForm(id, value) { + const game = await Game.findOne({ _id: gameId }); + + if (game.is_started) return; + if (!game.user1_id.equals(user._id) && !game.user2_id.equals(user._id)) return; + + const form = game.user1_id.equals(user._id) ? game.form2 : game.form1; + + const item = form.find(i => i.id == id); + + if (item == null) return; + + item.value = value; + + const set = game.user1_id.equals(user._id) ? { + form2: form + } : { + form1: form + }; + + await Game.update({ _id: gameId }, { + $set: set + }); + + publishOthelloGameStream(gameId, 'update-form', { + user_id: user._id, + id, + value + }); + } + + async function message(message) { + message.id = Math.random(); + publishOthelloGameStream(gameId, 'message', { + user_id: user._id, + message + }); + } + async function accept(accept: boolean) { const game = await Game.findOne({ _id: gameId }); diff --git a/src/web/app/common/views/components/othello.room.vue b/src/web/app/common/views/components/othello.room.vue index dfdc43ef96..bdefcdc49f 100644 --- a/src/web/app/common/views/components/othello.room.vue +++ b/src/web/app/common/views/components/othello.room.vue @@ -2,37 +2,77 @@ <div class="root"> <header><b>{{ game.user1.name }}</b> vs <b>{{ game.user2.name }}</b></header> - <p>ゲームの設定</p> + <div> + <p>ゲームの設定</p> - <el-select class="map" v-model="mapName" placeholder="マップを選択" @change="onMapChange"> - <el-option label="ランダム" :value="null"/> - <el-option-group v-for="c in mapCategories" :key="c" :label="c"> - <el-option v-for="m in maps" v-if="m.category == c" :key="m.name" :label="m.name" :value="m.name"> - <span style="float: left">{{ m.name }}</span> - <span style="float: right; color: #8492a6; font-size: 13px" v-if="m.author">(by <i>{{ m.author }}</i>)</span> - </el-option> - </el-option-group> - </el-select> + <el-card class="map"> + <div slot="header"> + <el-select :class="$style.mapSelect" v-model="mapName" placeholder="マップを選択" @change="onMapChange"> + <el-option label="ランダム" :value="null"/> + <el-option-group v-for="c in mapCategories" :key="c" :label="c"> + <el-option v-for="m in maps" v-if="m.category == c" :key="m.name" :label="m.name" :value="m.name"> + <span style="float: left">{{ m.name }}</span> + <span style="float: right; color: #8492a6; font-size: 13px" v-if="m.author">(by <i>{{ m.author }}</i>)</span> + </el-option> + </el-option-group> + </el-select> + </div> + <div :class="$style.board" v-if="game.settings.map != null" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }"> + <div v-for="(x, i) in game.settings.map.join('')" + :class="{ none: x == ' ' }" + @click="onPixelClick(i, x)" + > + <template v-if="x == 'b'">%fa:circle%</template> + <template v-if="x == 'w'">%fa:circle R%</template> + </div> + </div> + </el-card> - <div class="board" v-if="game.settings.map != null" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }"> - <div v-for="(x, i) in game.settings.map.join('')" - :class="{ none: x == ' ' }" - @click="onPixelClick(i, x)" - > - <template v-if="x == 'b'">%fa:circle%</template> - <template v-if="x == 'w'">%fa:circle R%</template> - </div> - </div> - - <div class="rules"> - <mk-switch v-model="game.settings.is_llotheo" @change="updateSettings" text="石の少ない方が勝ち(ロセオ)"/> - <mk-switch v-model="game.settings.looped_board" @change="updateSettings" text="ループマップ"/> - <mk-switch v-model="game.settings.can_put_everywhere" @change="updateSettings" text="どこでも置けるモード"/> - <div> + <el-card class="bw"> + <div slot="header"> + <span>先手/後手</span> + </div> <el-radio v-model="game.settings.bw" label="random" @change="updateSettings">ランダム</el-radio> <el-radio v-model="game.settings.bw" :label="1" @change="updateSettings">{{ game.user1.name }}が黒</el-radio> <el-radio v-model="game.settings.bw" :label="2" @change="updateSettings">{{ game.user2.name }}が黒</el-radio> - </div> + </el-card> + + <el-card class="rules"> + <div slot="header"> + <span>ルール</span> + </div> + <mk-switch v-model="game.settings.is_llotheo" @change="updateSettings" text="石の少ない方が勝ち(ロセオ)"/> + <mk-switch v-model="game.settings.looped_board" @change="updateSettings" text="ループマップ"/> + <mk-switch v-model="game.settings.can_put_everywhere" @change="updateSettings" text="どこでも置けるモード"/> + </el-card> + + <el-card class="bot-form" v-if="form"> + <div slot="header"> + <span>Botの設定</span> + </div> + <el-alert v-for="message in messages" + :title="message.text" + :type="message.type" + :key="message.id" + /> + <template v-for="item in form"> + <mk-switch v-if="item.type == 'button'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm($event, item)">{{ item.desc || '' }}</mk-switch> + + <el-card v-if="item.type == 'radio'" :key="item.id"> + <div slot="header"> + <span>{{ item.label }}</span> + </div> + <el-radio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :label="r.value" @change="onChangeForm($event, item)">{{ r.label }}</el-radio> + </el-card> + + <el-card v-if="item.type == 'textbox'" :key="item.id"> + <div slot="header"> + <span>{{ item.label }}</span> + </div> + <el-input v-model="item.value" @change="onChangeForm($event, item)"/> + </el-card> + </template> + </el-card> </div> <footer> @@ -64,7 +104,9 @@ export default Vue.extend({ o: null, isLlotheo: false, mapName: maps.eighteight.name, - maps: maps + maps: maps, + form: null, + messages: [] }; }, @@ -88,11 +130,56 @@ export default Vue.extend({ created() { this.connection.on('change-accepts', this.onChangeAccepts); this.connection.on('update-settings', this.onUpdateSettings); + this.connection.on('init-form', this.onInitForm); + this.connection.on('message', this.onMessage); + + if (this.game.user1_id != (this as any).os.i.id && this.game.settings.form1) this.form = this.game.settings.form1; + if (this.game.user2_id != (this as any).os.i.id && this.game.settings.form2) this.form = this.game.settings.form2; + + // for debugging + if ((this as any).os.i.username == 'test1') { + setTimeout(() => { + this.connection.send({ + type: 'init-form', + body: [{ + id: 'button1', + type: 'button', + label: 'Enable hoge', + value: false + }, { + id: 'radio1', + type: 'radio', + label: '強さ', + value: 2, + items: [{ + label: '弱', + value: 1 + }, { + label: '中', + value: 2 + }, { + label: '強', + value: 3 + }] + }] + }); + + this.connection.send({ + type: 'message', + body: { + text: 'Hey', + type: 'info' + } + }); + }, 2000); + } }, beforeDestroy() { this.connection.off('change-accepts', this.onChangeAccepts); this.connection.off('update-settings', this.onUpdateSettings); + this.connection.off('init-form', this.onInitForm); + this.connection.off('message', this.onMessage); }, methods: { @@ -135,6 +222,24 @@ export default Vue.extend({ } }, + onInitForm(x) { + if (x.user_id == (this as any).os.i.id) return; + this.form = x.form; + }, + + onMessage(x) { + if (x.user_id == (this as any).os.i.id) return; + this.messages.unshift(x.message); + }, + + onChangeForm(v, item) { + this.connection.send({ + type: 'update-form', + id: item.id, + value: v + }); + }, + onMapChange(v) { if (v == null) { this.game.settings.map = null; @@ -168,40 +273,21 @@ export default Vue.extend({ .root text-align center + background #f9f9f9 > header padding 8px border-bottom dashed 1px #c4cdd4 - > .map - width 300px + > div + padding 0 16px - > .board - display grid - grid-gap 4px - width 300px - height 300px - margin 16px auto - - > div - background transparent - border solid 2px #ddd - border-radius 6px - overflow hidden - cursor pointer - - * - pointer-events none - user-select none - width 100% - height 100% - - &.none - border-color transparent - - > .rules - max-width 300px - margin 0 auto 32px auto + > .map + > .bw + > .rules + > .bot-form + max-width 400px + margin 0 auto 16px auto > footer position sticky @@ -213,3 +299,37 @@ export default Vue.extend({ > .status margin 0 0 16px 0 </style> + +<style lang="stylus" module> +.mapSelect + width 100% + +.board + display grid + grid-gap 4px + width 300px + height 300px + margin 0 auto + + > div + background transparent + border solid 2px #ddd + border-radius 6px + overflow hidden + cursor pointer + + * + pointer-events none + user-select none + width 100% + height 100% + + &.none + border-color transparent + +</style> + +<style lang="stylus"> +.el-alert__content + position initial !important +</style>