vue使用python
by Neo Ighodaro
由新Ighodaro
如何使用Python和Vue创建两人游戏 (How to create a two-player game with Python and Vue)
In this tutorial, we will create a realtime tic-tac-toe game using Python and Pusher channels. Here’s a demo of how the game will look and behave upon creation:
在本教程中,我们将使用Python和Pusher通道创建一个实时井字游戏。 以下是游戏制作后的外观和行为演示:
You will need Python 3+, virtualenv, and Flask installed on your machine.The advent of the PC and the internet has redefined the term “entertainment” and the means by which it can be obtained. While a console or some special hardware would have been required to play games in the past, games are only a click away in today’s world of technology.
您将需要在计算机上安装Python 3 +,virtualenv和Flask。PC和Internet的出现重新定义了“娱乐”一词及其获取方式。 尽管过去需要使用控制台或某些特殊的硬件才能玩游戏,但在当今的技术世界中,只需单击一下鼠标即可。
This multiplayer game will allow a player to connect using their preferred username (or generate a random username where a player doesn’t connect with a username) and choose to play with another player from a list of other online players.
这款多人游戏将允许玩家使用其首选用户名进行连接(或在不与用户名建立联系的情况下生成随机用户名),并从其他在线玩家列表中选择与另一位玩家一起玩。
The game itself follows the conventional principles of the popular tic-tac-toe game. The “online player(s)” feature is powered by Pusher presence channels and the realtime updates of a player’s move across multiple windows is powered by Pusher private channels. The source code for this tutorial is available here GitHub.
游戏本身遵循流行的井字游戏的传统原理。 “在线播放器”功能由Pusher存在通道支持 ,而播放器跨多个窗口的移动的实时更新由Pusher专用通道支持。 该教程的源代码可在GitHub上找到 。
Let’s get started.
让我们开始吧。
先决条件 (Prerequisites)
To follow along, a basic knowledge of Python, Flask, JavaScript (ES6 syntax) and Vue is required. You will also need the following installed on your machine:
接下来,需要具备Python,Flask,JavaScript(ES6语法)和Vue的基础知识。 您还将需要在计算机上安装以下软件:
Python (v3.x)
Python(v3.x)
Virtualenv
虚拟环境
Flask
烧瓶
Virtualenv is great for creating isolated Python environments, so we can install dependencies in an isolated environment without polluting our global packages directory.
Virtualenv非常适合创建隔离的Python环境,因此我们可以在隔离的环境中安装依赖项而不会污染我们的全局包目录。
搭建环境 (Setting up the environment)
We will create the project folder and activate a virtual environment within it:
我们将创建项目文件夹并在其中激活虚拟环境:
$ mkdir python-pusher-mutiplayer-game$ cd python-pusher-mutiplayer-game$ virtualenv .venv$ source .venv/bin/activate # Linux based systems$ \path\to\env\Scripts\activate # Windows users
We will install Flask using this command:
我们将使用以下命令安装Flask :
$ pip install flask
设置推送器 (Setting up Pusher)
To integrate Pusher into the multiplayer game, we need to create a Pusher channels application from the Pusher dashboard. If you don’t already have a Pusher account, head over to the Pusher website and create one.
要将Pusher集成到多人游戏中,我们需要从Pusher仪表板创建Pusher频道应用程序。 如果您还没有Pusher帐户,请转到Pusher网站并创建一个。
After creating an account, create a new channels application and enable client events from the application dashboard. To enable client events, click on App settings and scroll to the bottom of the page then select the option that says Enable client events, and update the App settings.
创建帐户后,创建一个新的渠道应用程序并从应用程序仪表板启用客户端事件。 要启用客户端事件,请单击“ 应用程序设置”并滚动到页面底部,然后选择“ 启用客户端事件 ”选项,然后更新应用程序设置。
构建后端服务器 (Building the backend server)
Back in the project directory, let’s install the Python Pusher library with this command:
回到项目目录,让我们使用以下命令安装Python Pusher库 :
$ pip install pusher
We will create a new file and call it app.py
, this is where we will write all the code for the Flask backend server. We will also create a folder and call it templates
, this folder will hold the markup files for this application.
我们将创建一个新文件并将其app.py
,这是我们将编写Flask后端服务器的所有代码的地方。 我们还将创建一个文件夹并将其称为templates
,该文件夹将保存此应用程序的标记文件。
Let’s write some code to register the endpoints for the game and serve the view, open the app.py
file and paste the following code:
让我们编写一些代码来注册游戏的端点并提供视图,打开app.py
文件并粘贴以下代码:
// File: ./app.pyfrom flask import Flask, render_template, request, jsonify, make_response, jsonfrom pusher import pusherapp = Flask(__name__)pusher = pusher_client = pusher.Pusher(app_id='PUSHER_APP_ID',key='PUSHER_APP_KEY',secret='PUSHER_APP_SECRET',cluster='PUSHER_APP_CLUSTER',ssl=True)name = ''@app.route('/')def index():return render_template('index.html')@app.route('/play')def play():global namename = request.args.get('username')return render_template('play.html')@app.route("/pusher/auth", methods=['POST'])def pusher_authentication():auth = pusher.authenticate(channel=request.form['channel_name'],socket_id=request.form['socket_id'],custom_data={u'user_id': name,u'user_info': {u'role': u'player'}})return json.dumps(auth)if __name__ == '__main__':app.run(host='0.0.0.0', port=5000, debug=True)name = ''
Replace the
PUSHER_APP_*
keys with the values on your Pusher dashboard.将
PUSHER_APP_*
键替换为Pusher仪表板上的值。
In the code above, we defined three endpoints, here’s what they do:
在上面的代码中,我们定义了三个端点,这是它们的作用:
/
- renders the front page that asks a player to connect with a username./
-呈现要求玩家使用用户名进行连接的首页。/play
- renders the game view./play
play-渲染游戏视图。/pusher/auth
- authenticates Pusher’s presence and private channels for connected players./pusher/auth
为连接的播放/pusher/auth
验证Pusher的状态和专用通道。
建立前端 (Building the frontend)
In the templates
folder, we will create two files:
在templates
文件夹中,我们将创建两个文件:
index.html
index.html
play.html
play.html
The index.html
file will render the connection page, so open the templates/index.html
file and paste the following code:
index.html
文件将呈现连接页面,因此请打开templates/index.html
文件并粘贴以下代码:
<!-- File: ./templates/index.html --><!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><meta name="description" content=""><meta name="author" content="Neo Ighodaro"><title>TIC-TAC-TOE</title><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"><style>:root {--input-padding-x: .75rem;--input-padding-y: .75rem;}html,body, body > div {height: 100%;}body > div {display: -ms-flexbox;display: flex;-ms-flex-align: center;align-items: center;padding-top: 40px;padding-bottom: 40px;background-color: #f5f5f5;}.form-signin {width: 100%;max-width: 420px;padding: 15px;margin: auto;}.form-label-group {position: relative;margin-bottom: 1rem;}.form-label-group > input,.form-label-group > label {padding: var(--input-padding-y) var(--input-padding-x);}.form-label-group > label {position: absolute;top: 0;left: 0;display: block;width: 100%;margin-bottom: 0; /* Override default `<label>` margin */line-height: 1.5;color: #495057;cursor: text; /* Match the input under the label */border: 1px solid transparent;border-radius: .25rem;transition: all .1s ease-in-out;}.form-label-group input::-webkit-input-placeholder {color: transparent;}.form-label-group input:-ms-input-placeholder {color: transparent;}.form-label-group input::-ms-input-placeholder {color: transparent;}.form-label-group input::-moz-placeholder {color: transparent;}.form-label-group input::placeholder {color: transparent;}.form-label-group input:not(:placeholder-shown) {padding-top: calc(var(--input-padding-y) + var(--input-padding-y) * (2 / 3));padding-bottom: calc(var(--input-padding-y) / 3);}.form-label-group input:not(:placeholder-shown) ~ label {padding-top: calc(var(--input-padding-y) / 3);padding-bottom: calc(var(--input-padding-y) / 3);font-size: 12px;color: #777;}</style></head><body><div id="app"><form class="form-signin"><div class="text-center mb-4"><img class="mb-4" src="https://thestore.gameops.com/v/vspfiles/photos/Tic-Tac-Go-14.gif" alt="" width="72" height="72"><h1 class="h3 mb-3 font-weight-normal">TIC-TAC-TOE</h1><p>PUT IN YOUR DETAILS TO PLAY</p></div><div class="form-label-group"><input type="name" id="inputUsername" ref="username" class="form-control" placeholder="Username" required="" autofocus=""><label for="inputUsername">Username</label></div><div class="form-label-group"><input type="email" id="inputEmail" ref="email" class="form-control" placeholder="Email address" autofocus="" required><label for="inputEmail">Email address</label></div><button class="btn btn-lg btn-primary btn-block" type="submit" @click.prevent="login">Connect</button><p class="mt-5 mb-3 text-muted text-center">© 2017-2018</p></form></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script>var app = new Vue({el: '#app',methods: {login: function () {let username = this.$refs.username.valuelet email = this.$refs.email.valuewindow.location.replace(`/play?username=${username}&email=${email}`);}}})</script></body></html>
When a player visits the connection page and puts in a username and email, the browser window will be redirected to the game view.
当玩家访问连接页面并输入用户名和电子邮件时,浏览器窗口将被重定向到游戏视图。
Let’s write the markup for the game view. Open the play.html
file and paste the following code:
让我们为游戏视图编写标记。 打开play.html
文件并粘贴以下代码:
<!-- file: ./templates/play.html --><!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"><title>TIC-TAC-TOE</title></head><body><div id="app" class="container-fluid"><div class="container-fluid clearfix mb-3 shadow"><img class="float-left my-3" src="https://thestore.gameops.com/v/vspfiles/photos/Tic-Tac-Go-14.gif" height="62px" width="62px"/><div class="float-right w-25 py-3"><img class="my-3 mx-3 rounded-circle border" src="http://dfsanonymous.club/wp-content/uploads/2017/11/DFSAnonymous-NewLogo.png"height="62px" width="62px" /><p class="d-inline"> {% raw %} {{ username }} {% endraw %} </p></div></div><div class="row mx-5" style="height: 50vh"><div class="col-8 h-50 align-self-center"><div class="row border rounded invisible h-50 w-75 m-auto" style="font-size: 3.6rem" ref="gameboard" @click="playerAction"><div class="h-100 pr-2 col border border-dark" data-id="1" ref="1"></div><div class="col pr-2 border border-dark" data-id="2" ref="2"></div><div class="col pr-2 border border-dark" data-id="3" ref="3"></div><div class="w-100"></div><div class="h-100 pr-2 col border border-dark" data-id="4" ref="4"></div><div class="col pr-2 border border-dark" data-id="5" ref="5"></div><div class="col pr-2 border border-dark" data-id="6" ref="6"></div><div class="w-100"></div><div class="h-100 pr-2 col border border-dark" data-id="7" ref="7"></div><div class="col pr-2 border border-dark" data-id="8" ref="8"></div><div class="col pr-2 border border-dark" data-id="9" ref="9"></div></div></div><div class="col-4 pl-3"><div class="row h-100"><div class="col border h-75 text-center" style="background: rgb(114, 230, 147);"><p class="my-3"> {% raw %} {{ players }} {% endraw %} online player(s) </p><hr/><li class="m-auto py-3 text-dark" style="cursor: pointer;" v-for="member in connectedPlayers" @click="choosePlayer">{% raw %} {{ member }} {% endraw %}</li></div><div class="w-100"></div><div class="col text-center py-3 border h-25" style="background: #b6c0ca; font-size: 1em; font-weight: bold">{% raw %} {{ status }} {% endraw %}</div></div></div></div></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script src="https://js.pusher.com/4.2/pusher.min.js"></script><script></script></body></html>
The code above defines the layout of the game view but does not contain any interactivity or realtime features. In the scripts section, before the closing body
tag, we included the Vue and Pusher libraries because they are required for the game to work.
上面的代码定义了游戏视图的布局,但不包含任何交互性或实时功能。 在脚本部分的结束body
标签之前,我们包含了Vue和Pusher库,因为它们是游戏正常运行所必需的。
Let’s include the JavaScript code that will drive the entire game process and define its logic.
让我们包括将驱动整个游戏过程并定义其逻辑JavaScript代码。
In the same file, add the code below in between the script
tag that is just before the closing body
tag:
在同一文件中,将以下代码添加在script
标签之间,即结束body
标签之前:
var app = new Vue({el: '#app',data: {username: '',players: 0,connectedPlayers: [],status: '',pusher: new Pusher('PUSHER_APP_KEY', {authEndpoint: '/pusher/auth',cluster: 'PUSHER_APP_CLUSTER',encrypted: true}),otherPlayerName: '',mychannel: {},otherPlayerChannel: {},firstPlayer: 0,turn: 0,boxes: [0, 0, 0, 0, 0, 0, 0, 0, 0]},created () {let url = new URL(window.location.href);let name = url.searchParams.get("username");if (name) {this.username = namethis.subscribe();this.listeners();} else {this.username = this.generateRandomName();location.assign("/play?username=" + this.username);}},methods: {// We will add methods here}});
Replace the
PUSHER_APP_*
keys with the keys on your Pusher dashboard.将
PUSHER_APP_*
键替换为Pusher仪表板上的键。
Above, we create a new instance of Vue and we target the #app
selector. We define all the defaults in the data
object and then in the create()
function which is called automatically when the Vue component is created, we check for a user and assign the user to the username if one was supplied.
上面,我们创建了Vue的新实例,并以#app
选择器为目标。 我们在data
对象中定义所有默认值,然后在create()
Vue组件时自动调用的create()
函数中,检查用户并将用户分配给用户名(如果提供了用户名)。
We also make calls to the subscribe
and listeners
methods. Let’s define those inside the methods
object. Inside the methods
object, paste the following functions:
我们还调用subscribe
和listeners
方法。 让我们在methods
对象中定义它们。 在methods
对象内,粘贴以下函数:
// [...]subscribe: function () {let channel = this.pusher.subscribe('presence-channel');this.myChannel = this.pusher.subscribe('private-' + this.username)channel.bind('pusher:subscription_succeeded', (player) => {this.players = player.count - 1player.each((player) => {if (player.id != this.username)this.connectedPlayers.push(player.id)});});channel.bind('pusher:member_added', (player) => {this.players++;this.connectedPlayers.push(player.id)});channel.bind('pusher:member_removed', (player) => {this.players--;var index = this.connectedPlayers.indexOf(player.id);if (index > -1) {this.connectedPlayers.splice(index, 1)}});},listeners: function () {this.pusher.bind('client-' + this.username, (message) => {if (confirm('Do you want to start a game of Tic Tac Toe with ' + message)) {this.otherPlayerName = messagethis.otherPlayerChannel = this.pusher.subscribe('private-' + this.otherPlayerName)this.otherPlayerChannel.bind('pusher:subscription_succeeded', () => {this.otherPlayerChannel.trigger('client-game-started', this.username)})this.startGame(message)} else {this.otherPlayerChannel = this.pusher.subscribe('private-' + message)this.otherPlayerChannel.bind('pusher:subscription_succeeded', () => {this.otherPlayerChannel.trigger('client-game-declined', "")})this.gameDeclined()}}),this.myChannel.bind('client-game-started', (message) => {this.status = "Game started with " + messagethis.$refs.gameboard.classList.remove('invisible');this.firstPlayer = 1;this.turn = 1;})this.myChannel.bind('client-game-declined', () => {this.status = "Game declined"})this.myChannel.bind('client-new-move', (position) => {this.$refs[position].innerText = this.firstPlayer ? 'O' : 'X'})this.myChannel.bind('client-your-turn', () => {this.turn = 1;})this.myChannel.bind('client-box-update', (update) => {this.boxes = update;})this.myChannel.bind('client-you-lost', () => {this.gameLost();})},// [...]
In the subscribe
method, we subscribe to our Pusher presence channel, and then subscribe to the private channel for the current user. In the listeners
method we register the listeners for all the events we are expecting to be triggered on the private channel we subscribed to.
在subscribe
方法中,我们订阅Pusher在线状态频道,然后订阅当前用户的专用频道。 在listeners
方法中,我们为希望在订阅的私有频道上触发的所有事件注册侦听器。
Next, we will add other helper methods to our methods class. Inside the methods class, add the following functions to the bottom after the listeners
method:
接下来,我们将其他辅助方法添加到我们的方法类中。 在方法类内部,将以下函数添加到listeners
方法的底部:
// Generates a random string we use as a name for a guest usergenerateRandomName: function () {let text = '';let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';for (var i = 0; i < 6; i++) {text += possible.charAt(Math.floor(Math.random() * possible.length));}return text;},// Lets you choose a player to play as.choosePlayer: function (e) {this.otherPlayerName = e.target.innerTextthis.otherPlayerChannel = this.pusher.subscribe('private-' + this.otherPlayerName)this.otherPlayerChannel.bind('pusher:subscription_succeeded', () => {this.otherPlayerChannel.trigger('client-' + this.otherPlayerName, this.username)});},// Begins the gamestartGame: function (name) {this.status = "Game started with " + namethis.$refs.gameboard.classList.remove('invisible');},// User declined to playgameDeclined: function () {this.status = "Game declined"},// Game has ended with current user winninggameWon: function () {this.status = "You WON!"this.$refs.gameboard.classList.add('invisible');this.restartGame()},// Game has ended with current user losinggameLost: function () {this.turn = 1;this.boxes = [0, 0, 0, 0, 0, 0, 0, 0, 0]this.status = "You LOST!"this.$refs.gameboard.classList.add('invisible');this.restartGame()},// Restarts a gamerestartGame: function () {for (i = 1; i < 10; i++) {this.$refs[i].innerText = ""}this.$refs.gameboard.classList.remove('invisible');},// Checks tiles to see if the tiles passed are a matchcompare: function () {for (var i = 1; i < arguments.length; i++) {if (arguments[i] === 0 || arguments[i] !== arguments[i - 1]) {return false}}return true;},// Checks the tiles and returns true if theres a winning playtheresAMatch: function () {return this.compare(this.boxes[0], this.boxes[1], this.boxes[2]) ||this.compare(this.boxes[3], this.boxes[4], this.boxes[5]) ||this.compare(this.boxes[6], this.boxes[7], this.boxes[8]) ||this.compare(this.boxes[0], this.boxes[3], this.boxes[6]) ||this.compare(this.boxes[1], this.boxes[4], this.boxes[7]) ||this.compare(this.boxes[2], this.boxes[5], this.boxes[8]) ||this.compare(this.boxes[2], this.boxes[4], this.boxes[6]) ||this.compare(this.boxes[0], this.boxes[4], this.boxes[8])},// Checks to see if the play was a winning playplayerAction: function (e) {let index = e.target.dataset.id - 1let tile = this.firstPlayer ? 'X' : 'O'if (this.turn && this.boxes[index] == 0) {this.turn = 0this.boxes[index] = tilee.target.innerText = tilethis.otherPlayerChannel.trigger('client-your-turn', "")this.otherPlayerChannel.trigger('client-box-update', this.boxes)this.otherPlayerChannel.trigger('client-new-move', e.target.dataset.id)if (this.theresAMatch()) {this.gameWon()this.boxes = [0, 0, 0, 0, 0, 0, 0, 0, 0]this.otherPlayerChannel.trigger('client-you-lost', '')}}},
Above, we have added several helper methods that the game needs to function properly and before each method, we have added a comment to show what the method does.
上面,我们添加了游戏需要正常运行的几种辅助方法,在每种方法之前,我们添加了注释以显示该方法的功能。
Let’s test the game now.
让我们现在测试游戏。
测试游戏 (Testing the game)
We can test the game by running this command:
我们可以通过运行以下命令来测试游戏:
$ flask run
Now if we visit localhost:5000, we should see the connection page and test the game:
现在,如果我们访问localhost:5000 ,我们应该看到连接页面并测试游戏:
结论 (Conclusion)
In this tutorial, we have learned how to leverage the Pusher SDK in creating an online multiplayer game powered by a Python backend server.
在本教程中,我们学习了如何利用Pusher SDK创建由Python后端服务器提供支持的在线多人游戏。
The source code for this tutorial is available on GitHub
本教程的源代码可在GitHub上找到
This post first appeared on the Pusher Blog
这篇文章首先出现在Pusher Blog上
翻译自: https://www.freecodecamp.org/news/how-to-create-a-two-player-game-with-python-and-vue-4220c5592d53/
vue使用python