電影《動物世界》對戰系統(Javascript)

最近刷了一部電影《動物世界》,感概原來簡單的「剪刀石頭布」遊戲還能夠這麼燒腦,強大的數據分析能力、對人性心理的靈敏嗅覺等。看完以後饒有興致,因而便利用socket技術,實現了一個「動物世界」多人對戰系統。前端

遊戲背景

圖片描述

故事講述的是男主被髮小欺騙,欠下了一屁股債,爲了償還債務被迫上了一艘賊船,同時上船的還有一批人,大夥兒的情況都差很少。上船的好處是有機會還清債務而且還可能得到一筆巨大的財富,這對於在現實世界中已經生活不能自理的人來講,無疑是一次改變人生的機會,而一旦失敗的話,就要被拉去作人體實驗(恐怖如斯)。vue

上船的人會進行一場賭博,就是咱們小時候常玩的「石頭剪刀布」,每人初始時擁有12張卡牌,石頭、剪刀、布各4張,而且擁有3顆星,你們能夠找任何一我的做爲對手,每人各出一張卡牌,獲勝者將從失敗方拿走一顆星。遊戲獲勝條件是手裏卡牌所有消耗完而且擁有的星星很多於3顆,反之,若卡牌消耗完且星星少於3顆、或還有卡牌但星星爲0,都視爲失敗。node

後端邏輯

咱們將利用koa來搭建一個socket服務器,來管理客戶端的消息接受和分發。git

主要邏輯:github

const io = SocketIO(server) // 創建socket鏈接
const users = {} // 緩存當前連接的用戶
const challengeData = {} // 緩存用戶發起的對戰信息

io.on('connection', socket => { // 客戶端鏈接後
  const id = socket.id // 當前鏈接的unique標識

  socket.emit('connected') // 告訴客戶端已經鏈接成功

  // 接收客戶端的open事件
  socket.on('open', name => {
    // 初始化數據
    users[id] = {
      id,
      name, // 用戶暱稱
      star: 3, // 用戶擁有的星星
      stone: 4, // 用戶擁有的「石頭」卡牌數量
      scissors: 4, // 用戶擁有的「剪刀」卡牌數量
      paper: 4 // 用戶擁有的「布」卡牌數量
    }

    // 通知全部人,當前全部用戶的信息
    io.emit('update_users', users)
  })

  // 用戶發起挑戰
  socket.on('challenge', data => {
    // data包括fromCard(發起者出示的卡牌)、toId(被挑戰者的id)
    data.fromId = id
    challengeData[id] = data
    io.to(data.toId).emit('accept_challenge', users[id]) // 告訴對方有人要和你對戰
  })

  // 發起者取消了挑戰
  socket.on('cancel_challenge', () => {
    io.to(challengeData[id].toId).emit('cancel_challenge') // 告訴對方挑戰已取消
    delete challengeData[id] // 刪除緩存的數據
  })

  // 對方接受挑戰的信息
  socket.on('respond_challenge', data => {
    if (data.accept) { // 接受
      let cd = challengeData[data.fromId]
      cd.toCard = data.toCard // 被挑戰者出示的卡牌

        // 雙方卡牌各減小1
      users[cd.fromId][cd.fromCard]--
      users[cd.toId][cd.toCard]--

      let result = getChallengeResult(cd.fromCard, cd.toCard) // 得到挑戰結果

        // 比賽後的星星變動
      if (result === 1) {
        users[cd.fromId].star++
        users[cd.toId].star--
      } else if (result === -1) {
        users[cd.fromId].star--
        users[cd.toId].star++
      }

        // 告訴挑戰者和被挑戰者,比賽的結果
      io.to(cd.fromId).emit('result_challenge', result, users)
      io.to(cd.toId).emit('result_challenge', -result, users)
    } else { // 拒絕
      io.to(data.fromId).emit('cancel_challenge') // 告訴發起者對方不接受挑戰
    }

    delete challengeData[data.fromId]
  })

  // 比賽勝利
  socket.on('success_challenge', () => {
    // 告訴全部人,有人得到了勝利
    socket.broadcast.emit('success_challenge', users[id])
  })

  // 斷開鏈接
  socket.on('disconnect', () => {
    delete users[id]
    // 廣播用戶已退出
    socket.broadcast.emit('update_users', users)
  })
})

前端邏輯

前端使用Vue來進行頁面渲染。後端

import request from '@/common/request'
import tips from '@axe/tips'
import modal from '@axe/modal'

import Loading from './components/Loading.vue'

/* eslint-disable no-alert */
export default {
  name: 'App',
  components: {
    Loading
  },
  data () {
    return {
      isConnected: false,
      id: '',
      selectedUserId: '',
      selectedCard: '',
      users: {},
      acceptChallenge: false
    };
  },
  computed: {
    userInfo () {
      let user = this.users[this.id] || {}

      return {
        star: user.star || 0,
        stone: user.stone || 0,
        scissors: user.scissors || 0,
        paper: user.paper || 0
      }
    },
    totalInfo () {
      let info = {
        stone: 0,
        scissors: 0,
        paper: 0
      }

      for (let id in this.users) {
        let user = this.users[id]

        info.stone += user.stone || 0
        info.scissors += user.scissors || 0
        info.paper += user.paper || 0
      }

      return info
    }
  },
  methods: {
    handleSelectCard (type) {
      this.selectedCard = type
    },
    handleSelectUser (id) {
      if (this.id === id) {
        tips.show({
          content: '不能夠挑戰本身哦'
        })
        return
      }

      this.selectedUserId = id
    },
    handleChallenge () {
      if (this.gameover) {
        tips.show({
          content: '遊戲已結束,請從新開始'
        })
        return
      }

      if (!this.selectedCard) {
        tips.show({
          content: '請挑選卡牌'
        })
        return
      }

      if (this.users[this.id][this.selectedCard] <= 0) {
        tips.show({
          content: '這類卡牌已耗盡'
        })
        return
      }

      let user = this.users[this.selectedUserId]

      if (!user) {
        tips.show({
          content: '請挑選對手'
        })
        return
      }

      if (user.star <= 0 || (user.stone + user.scissors + user.paper) <= 0) {
        tips.show({
          content: '該用戶已不具有對戰能力了'
        })
        return
      }

      if (!this.acceptChallenge) {
        this.socket.emit('challenge', {
          fromCard: this.selectedCard,
          toId: this.selectedUserId
        })

        modal.show({
          title: '發起挑戰',
          content: '等待對方接受中...',
          confirmText: '取消挑戰'
        }, t => {
          if (t === 'confirm') {
            this.socket.emit('cancel_challenge')
          }
        })
      } else {
        this.socket.emit('respond_challenge', {
          accept: true,
          fromId: this.challengeFromUser.id,
          toCard: this.selectedCard
        })

        // 重置記錄
        this.acceptChallenge = false
      }
    }
  },
  mounted () {
    request({
      url: '/api/info'
    }).then(data => {
      // 使用ip創建鏈接,局域網內其餘設備也能夠訪問
      this.socket = window.io.connect('http://' + data.ip + ':' + data.port)

      this.socket.on('connected', () => {
        let name = window.prompt('請輸入您優雅高貴的稱呼')

        if (!name || !name.trim()) {
          name = this.socket.id
        }

            // 告訴服務器,有人進來了
        this.socket.emit('open', name)

        // 已鏈接
        this.id = this.socket.id
        this.isConnected = true
      })

      this.socket.on('update_users', users => {
        this.users = users
      })

      // 是否接受挑戰
      this.socket.on('accept_challenge', fromUser => {
        modal.show({
          title: '接受挑戰',
          content: '是否接受來自【' + fromUser.name + '】的挑戰?',
          confirmText: '接受',
          cancelText: '拒絕'
        }, t => {
          if (t === 'confirm') {
            // 緩存挑戰信息,等待用戶選擇出示的卡牌
            this.selectedUserId = fromUser.id
            this.acceptChallenge = true
            this.challengeFromUser = fromUser
          } else {
            this.socket.emit('respond_challenge', {
              accept: false,
              fromId: fromUser.id
            })
          }
        })
      })

      this.socket.on('cancel_challenge', () => {
        this.acceptChallenge = false

        modal.hide()
        tips.show({
          content: '對方取消了挑戰'
        })
      })

      // 監聽對戰結果
      this.socket.on('result_challenge', (result, users) => {
        this.users = users

        modal.hide()
        tips.show({
          content: result === 0 ? '平局' : (result === 1 ? '你贏了' : '你輸了')
        }, () => {
          // 檢測遊戲勝利和失敗條件
          let user = users[this.id]
          let cardCount = user.stone + user.scissors + user.paper

          if (user.star >= 3 && cardCount <= 0) {
            this.socket.emit('success_challenge')

            modal.show({
              title: '遊戲勝利',
              content: '恭喜你得到了勝利!',
              confirmText: '再來一局'
            }, t => {
              if (t === 'confirm') {
                window.location.reload()
              }
            })
          } else if (user.star <= 0 || cardCount <= 0) {
            this.gameover = true

            modal.show({
              title: '遊戲結束',
              content: user.star <= 0 ? '你已經沒有星星了' : '你已經沒有卡牌了',
              confirmText: '從新開始'
            }, t => {
              if (t === 'confirm') {
                window.location.reload()
              }
            })
          }
        })
      })

      // 接收系統廣播,有人挑戰成功的信息
      this.socket.on('success_challenge', user => {
        window.alert(`恭喜【${user.name}】挑戰成功,戰績(${user.star})顆星`)
      })
    })
  }
}

補充說明

遊戲預覽api

源碼地址:https://github.com/ansenhuang/node-socket緩存

相關文章
相關標籤/搜索