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集成到多人游戲中,我們需要從Pu??sher儀表板創建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