资讯专栏INFORMATION COLUMN

电影《动物世界》对战系统(Javascript)

娣辩孩 / 2196人阅读

摘要:最近刷了一部电影动物世界,感概原来简单的剪刀石头布游戏还可以这么烧脑,强大的数据分析能力对人性心理的灵敏嗅觉等。看完之后饶有兴致,于是便利用技术,实现了一个动物世界多人对战系统。

最近刷了一部电影《动物世界》,感概原来简单的“剪刀石头布”游戏还可以这么烧脑,强大的数据分析能力、对人性心理的灵敏嗅觉等。看完之后饶有兴致,于是便利用socket技术,实现了一个“动物世界”多人对战系统。

游戏背景

故事讲述的是男主被发小欺骗,欠下了一屁股债,为了偿还债务被迫上了一艘贼船,同时上船的还有一批人,大伙儿的状况都差不多。上船的好处是有机会还清债务并且还可能获得一笔巨大的财富,这对于在现实世界中已经生活不能自理的人来说,无疑是一次改变人生的机会,而一旦失败的话,就要被拉去做人体实验(恐怖如斯)。

上船的人会进行一场赌博,就是我们小时候常玩的“石头剪刀布”,每人初始时拥有12张卡牌,石头、剪刀、布各4张,并且拥有3颗星,大家可以找任何一个人作为对手,每人各出一张卡牌,获胜者将从失败方拿走一颗星。游戏获胜条件是手里卡牌全部消耗完并且拥有的星星不少于3颗,反之,若卡牌消耗完且星星少于3颗、或还有卡牌但星星为0,都视为失败。

后端逻辑

我们将利用koa来搭建一个socket服务器,来管理客户端的消息接受和分发。

主要逻辑:

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})颗星`)
      })
    })
  }
}
补充说明

游戏预览

源码地址:https://github.com/ansenhuang/node-socket

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98887.html

相关文章

  • 《机器学习》作者Peter Flach:好莱坞也借AI上头条

    摘要:在高度结构化的数据挖掘以及通过分析来评估和改进机器学习模型方面,是国际领先的研究人员。在机器学习里,我并没有涉及强化学习的内容。这些准备让读者了解机器学习能做什么,然后我的书能帮助他们了解机器学习怎么工作。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/art... 访谈对象: Peter Flach,布里斯托大学人工智能教授,...

    MartinHan 评论0 收藏0
  • 《机器学习》作者Peter Flach:好莱坞也借AI上头条

    摘要:在高度结构化的数据挖掘以及通过分析来评估和改进机器学习模型方面,是国际领先的研究人员。在机器学习里,我并没有涉及强化学习的内容。这些准备让读者了解机器学习能做什么,然后我的书能帮助他们了解机器学习怎么工作。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/art... 访谈对象: Peter Flach,布里斯托大学人工智能教授,...

    haobowd 评论0 收藏0
  • 复杂的中国驾驶场景,正是深度学习的优势

    摘要:如果这个场景足够简单的话,深度学习并不能表现出相对于其它基于传统模式识别方法的优势。这是深度学习目前受到关注的一个非常重要的原因。通过积累大量的数据进行足够的训练,基于深度学习的系统可以给出最优规划。 谷歌和李世石的人机大战引爆了公众对于人工智能的关注,也让基于深度学习的人工智能成为汽车业界关注的重点,那么深度学习在智能驾驶的应用场景下有什么帮助呢?自动驾驶最先出现在美国,而不是欧洲或者日本...

    tracymac7 评论0 收藏0
  • 框架的游戏:2019 年 JavaScript 流行趋势

    摘要:年新星调查中显示,越来越流行,其热度已经逐渐超过了。及其框架位于全球最受欢迎使用最广泛的技术榜榜首。本文转载自框架的游戏年流行趋势英文原文JavaScript 生态系统复杂多变,各种框架让人眼花缭乱。究竟孰优孰劣,如今的发展趋势是怎样的,用人单位又需要怎样的人才?本文站在一个中立者的角度,客观分析了当前这场框架的游戏中,JavaScript 的流行趋势。 Javascript 的生态环境让我...

    mrcode 评论0 收藏0

发表评论

0条评论

娣辩孩

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<