サンプルを見る
📖 この記事のゴール
目標
Webサーバーと、クライアント側のアプリ間で通信する練習をしたい。
だから 〇×ゲーム(Tic tac toe)のサンプルプログラムを真似する。
1人2役で2窓で遊ぶ
情報
この記事は Lesson 1. から順に全部やってこないと ソースが足りず実行できないので注意されたい
What is | This is |
---|---|
Lesson 1. | 📖 DjangoとDockerでゲーム対局サーバーを作ろう! |
この記事のアーキテクチャ:
What is | This is |
---|---|
OS | Windows10 |
Container | Docker |
Database | Redis |
Program Language | Python 3 |
Web framework | Django |
Auth | allauth |
Frontend | Vuetify |
Data format | JSON |
Others | (Socket), Web socket |
Editor | Visual Studio Code (以下 VSCode と表記) |
以下、参考にした元記事は 📖Django Channels and WebSockets だ。
わたしの記事は単に やってみた ぐらいの位置づけだ
ディレクトリ構成を抜粋すると 以下のようになっている
├── 📂 src1 # あなたのDjangoサーバー開発用ディレクトリー。任意の名前
│ ├── 📂 apps1
│ │ ├── 📂 accounts_vol1o0 # アプリケーション
│ │ ├── 📂 portal_v1 # アプリケーション
│ │ └── 📂 practice_vol1o0 # アプリケーション
│ │ ├── 📂 management
│ │ ├── 📂 migrations
│ │ ├── 📂 models
│ │ ├── 📂 static
│ │ │ └── 📂 practice_vol1o0
│ │ │ └── 📂 data
│ │ │ └── 📂 desserts1
│ │ │ └── 📄 ver1o0.json
│ │ ├── 📂 templates
│ │ │ └── 📂 practice_vol1o0 # アプリケーションと同名
│ │ │ ├── 📂 prefecture
│ │ │ └── 📂 vuetifies
│ │ ├── 📂 views
│ │ │ ├── 📂 prefecture
│ │ │ └── 📂 vuetifies
│ │ ├── 📂 websocks
│ │ │ └── 📂 consumer
│ │ │ └── 📄 ver1o0.py
│ │ ├── 📄 __init__.py
│ │ ├── 📄 admin.py
│ │ ├── 📄 apps.py
│ │ └── 📄 tests.py
│ ├── 📂 data
│ ├── 📂 project1 # プロジェクト
│ │ ├── 📄 __init__.py
│ │ ├── 📄 asgi.py
│ │ ├── 📄 settings_secrets_example.txt
│ │ ├── 📄 settings.py
│ │ ├── 📄 urls_accounts_vol1o0.py
│ │ ├── 📄 urls_practice.py
│ │ ├── 📄 urls.py
│ │ └── 📄 wsgi.py
│ ├── 📂 project2 # プロジェクト
│ ├── 🐳 docker-compose-project2.yml
│ ├── 🐳 docker-compose.yml
│ ├── 🐳 Dockerfile
│ ├── 📄 manage.py
│ └── 📄 requirements.txt
├── 📂 src2_local # Djangoとは関係ないもの
│ ├── 📂 sockapp1
│ └── 📂 websockapp1
└── 📄 .gitignore
手順
Step [OA16o1o0g1o0] Dockerコンテナの起動
👇 (していなければ) Docker コンテナを起動しておいてほしい
# docker-compose.yml ファイルを置いてあるディレクトリーへ移動してほしい
cd src1
# Docker コンテナ起動
docker-compose up
Step [OA16o1o0g2o0] Pythonパッケージ インストール指定 - requirements.txt ファイル
👇 以下のファイルを編集してほしい
└── 📂 src1 # あなたの開発用ディレクトリー。任意の名前
👉 └── 📄 requirements.txt
# ...略...
# 以下を追加
# For tic-tac-toe-v1
channels_redis>=3.2
Step [OA16o1o0g3o0] Dockerコンテナの停止~ビルド~起動
👇 以下のコマンドを打鍵してほしい
# Dockerコンテナの停止
docker-compose down
# または Dockerマシンが走っているターミナルで `[Ctrl] + [C]` キーを打鍵
👇 以下のコマンドを打鍵してほしい
# requirements.txtを変更したので、Pythonパッケージのインストールをやり直します
docker-compose build
👇 以下のコマンドを打鍵してほしい
# Dockerコンテナの起動
docker-compose up
👆 これで Dockerコンテナに channels_redis パッケージをインストールした
Step [OA16o1o0g4o0] フォルダー作成 - apps1/tic_tac_toe_vol1o0 フォルダー
👇 以下のフォルダーを新規作成してほしい
└── 📂 src1
├── 📂 apps1
👉 │ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
└── 📄 requirements.txt
Step [OA16o1o0g5o0] アプリケーション作成
👇 以下のコマンドを打鍵してほしい
docker-compose run --rm web python manage.py startapp tic_tac_toe_vol1o0 ./apps1/tic_tac_toe_vol1o0
# ------------------ --------------------------
# 1 2
# 1. 任意のDjangoアプリケーション名
# 2. 既存のアプリケーション ディレクトリーへのパス
👇 以下のようなディレクトリー、ファイルが自動生成される
└── 📂 src1
└── 📂 apps1
👉 └── 📂 tic_tac_toe_vol1o0 # アプリケーション
👉 ├── 📂 migrations
👉 │ └── 📄 __init__.py
👉 ├── 📄 __init__.py
👉 ├── 📄 admin.py
👉 ├── 📄 apps.py
👉 ├── 📄 models.py
👉 ├── 📄 tests.py
👉 └── 📄 views.py
Step [OA16o1o0g6o0] 今回使わないファイルの削除
👇 以下のファイルを削除してほしい
└── 📂 src1
└── 📂 apps1
└── 📂 tic_tac_toe_vol1o0 # アプリケーション
├── 📂 migrations
│ └── 📄 __init__.py
├── 📄 __init__.py
├── 📄 admin.py
├── 📄 apps.py
👉 ├── 📄 models.py
├── 📄 tests.py
👉 └── 📄 views.py
Step [OA16o1o0g7o0] アプリケーション設定変更 - apps.py
👇 以下のファイルを編集してほしい
└── 📂 src1
└── 📂 apps1
└── 📂 tic_tac_toe_vol1o0 # アプリケーション
├── 📂 migrations
│ └── 📄 __init__.py
├── 📄 __init__.py
├── 📄 admin.py
👉 ├── 📄 apps.py
└── 📄 tests.py
from django.apps import AppConfig
class TicTacToeV1Config(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
# * 変更前
# name = 'tic_tac_toe_vol1o0'
# * OA16o1o0g7o0 変更後
name = 'apps1.tic_tac_toe_vol1o0'
# ------------------------
# 1
# 1. `src1/apps1/tic_tac_toe_vol1o0/apps.py`
# ------------------------
Step [OA16o1o0g8o0] アプリケーション登録 - settings.py ファイル
👇 以下の既存のファイルを編集してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📂 project1
👉 └── 📄 settings.py
# ...略...
INSTALLED_APPS = [
# あなたが追加したアプリケーション
# ...略...
# OA16o1o0g8o0 〇×ゲーム1.0巻
'apps1.tic_tac_toe_vol1o0',
# ...略...
]
Step [OA16o1o0g9o0] Web ページのスタイル作成 - style/main/ver1o0.css ファイル
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0 # アプリケーションと同名
│ │ └── 📂 style # ただのフォルダー
│ │ └── 📂 main # ただのフォルダー
👉 │ │ └── 📄 ver1o0.css
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📄 requirements.txt
/*
* OA16o1o0g9o0
* See also : 📖 [Django Channels and WebSockets](https://blog.logrocket.com/django-channels-and-websockets/)
*/
body {
/* width: 100%; */
height: 90vh;
background: #f1f1f1;
display: flex;
justify-content: center;
align-items: center;
}
#board {
display: grid;
grid-gap: 0.5em;
grid-template-columns: repeat(3, 1fr);
width: 16em;
height: auto;
margin: 0.5em 0;
}
.square {
background: #2f76c7;
width: 5em;
height: 5em;
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.5em;
font-weight: 500;
color: white;
box-shadow: 0.025em 0.125em 0.25em rgba(0, 0, 0, 0.25);
}
.head {
width: 16em;
text-align: center;
}
.wrapper h1,
h3 {
color: #0a2c1a;
}
label {
font-size: 20px;
color: #0a2c1a;
}
input,
select {
margin-bottom: 10px;
width: 100%;
padding: 15px;
border: 1px solid #125a33;
font-size: 14px;
background-color: #71d19e;
color: white;
}
.button {
color: white;
white-space: nowrap;
background-color: #31d47d;
padding: 10px 20px;
border: 0;
border-radius: 2px;
transition: all 150ms ease-out;
}
Step [OA16o1o0gA10o0] 機能作成 - scripts/play/ver1o0.js ファイル
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
👉 │ │ └── 📄 ver1o0.js
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📄 requirements.txt
// BOF OA16o1o0gA10o0
// See also: 📖[Django Channels and WebSockets](https://blog.logrocket.com/django-channels-and-websockets/)
let roomName = document.getElementById("board").getAttribute("room_name");
let myPiece = document.getElementById("board").getAttribute("my_piece");
let connectionString = `ws://${window.location.host}/tic-tac-toe/v1/playing/${roomName}/`;
// ----]----------------------- -----------------------------------
// 11 12 13
// ----------------------------------------------------------------
// 10
// 10. URL
// 11. スキーム : Web Socket
// 12. ホスト アドレス
// 13. パス
let webSock1 = new WebSocket(connectionString);
const PC_EMPTY = -1; // A square without piece; PC is piece
// Game board for maintaing the state of the game
let board = [PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY];
// SQ is square
// +---------+
// | 0 1 2 |
// | 3 4 5 |
// | 6 7 8 |
// +---------+
const SQ_0 = 0;
const SQ_1 = 1;
const SQ_2 = 2;
const SQ_3 = 3;
const SQ_4 = 4;
const SQ_5 = 5;
const SQ_6 = 6;
const SQ_7 = 7;
const SQ_8 = 8;
// Winning indexes.
arrayOfSquaresOfWinPattern = [
// +---------+
// | * * * |
// | . . . |
// | . . . |
// +---------+
[SQ_0, SQ_1, SQ_2],
// +---------+
// | . . . |
// | * * * |
// | . . . |
// +---------+
[SQ_3, SQ_4, SQ_5],
// +---------+
// | . . . |
// | . . . |
// | * * * |
// +---------+
[SQ_6, SQ_7, SQ_8],
// +---------+
// | * . . |
// | * . . |
// | * . . |
// +---------+
[SQ_0, SQ_3, SQ_6],
// +---------+
// | . * . |
// | . * . |
// | . * . |
// +---------+
[SQ_1, SQ_4, SQ_7],
// +---------+
// | . . * |
// | . . * |
// | . . * |
// +---------+
[SQ_2, SQ_5, SQ_8],
// +---------+
// | * . . |
// | . * . |
// | . . * |
// +---------+
[SQ_0, SQ_4, SQ_8],
// +---------+
// | . . * |
// | . * . |
// | * . . |
// +---------+
[SQ_2, SQ_4, SQ_6],
];
let countOfMove = 0; // Number of moves done
let myTurn = true; // Boolean variable to get the turn of the player.
// Add the click event listener on every block.
let elementArrayOfSquare = document.getElementsByClassName("square");
for (const element of elementArrayOfSquare) {
element.addEventListener("click", (event) => {
const sq = event.path[0].getAttribute("square"); // Square; 0 <= sq
if (board[sq] == PC_EMPTY) {
if (!myTurn) {
alert("Wait for other to place the move");
} else {
myTurn = false;
document.getElementById("alert_move").style.display = "none"; // Hide
makeMove(sq, myPiece);
}
}
});
}
/**
* Make a move
* @param {*} sq - Square; 0 <= sq
* @param {*} myPiece
* @returns
*/
function makeMove(sq, myPiece) {
sq = parseInt(sq);
let data = {
event: "MOVE",
message: {
index: sq,
player: myPiece,
},
};
if (board[sq] == PC_EMPTY) {
// if the valid move, update the board
// state and send the move to the server.
countOfMove++;
switch (myPiece) {
case "X":
board[sq] = 1;
break;
case "O":
board[sq] = 0;
break;
default:
alert(`Invalid my piece = ${myPiece}`);
return false;
}
webSock1.send(JSON.stringify(data));
}
// place the move in the game box.
elementArrayOfSquare[sq].innerHTML = myPiece;
// check for the winner
const gameOver = isGameOver();
if (myTurn) {
// if player winner, send the END event.
if (gameOver) {
data = {
event: "END",
message: `${myPiece} is a winner. Play again?`,
};
webSock1.send(JSON.stringify(data));
} else if (!gameOver && countOfMove == 9) {
data = {
event: "END",
message: "It's a draw. Play again?",
};
webSock1.send(JSON.stringify(data));
}
}
}
// function to reset the game.
function reset() {
board = [PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY, PC_EMPTY];
countOfMove = 0;
myTurn = true;
document.getElementById("alert_move").style.display = "inline";
for (const element of elementArrayOfSquare) {
element.innerHTML = "";
}
}
/**
* check if their is winning move
* @param {*} squaresOfWinPattern
* @returns
*/
function isPieceInLine(squaresOfWinPattern) {
return board[squaresOfWinPattern[0]] !== PC_EMPTY && board[squaresOfWinPattern[0]] === board[squaresOfWinPattern[1]] && board[squaresOfWinPattern[0]] === board[squaresOfWinPattern[2]];
}
/**
* function to check if player is winner.
* @returns I won
*/
function isGameOver() {
if (5 <= countOfMove) {
for (let squaresOfWinPattern of arrayOfSquaresOfWinPattern) {
if (isPieceInLine(squaresOfWinPattern)) {
return true;
}
}
}
return false;
}
/**
* Main function which handles the connection
* of websocket.
*/
function connect() {
// on websocket open, send the START event.
webSock1.onopen = () => {
// console.log("WebSockets connection created.");
webSock1.send(
JSON.stringify({
event: "START",
message: "",
})
);
};
webSock1.onclose = (e) => {
// console.log("Socket is closed. Reconnect will be attempted in 1 second.", e.reason);
setTimeout(function () {
connect();
}, 1000);
};
// Sending the info about the room
webSock1.onmessage = (e) => {
// On getting the message from the server
// Do the appropriate steps on each event.
let data = JSON.parse(e.data);
data = data["payload"];
let message = data["message"];
let event = data["event"];
switch (event) {
case "START":
console.log(`[Message] START e=${e.data}`); // ちゃんと動いているようなら消す
reset();
break;
case "END":
console.log(`[Message] END e=${e.data}`); // ちゃんと動いているようなら消す
alert(message);
reset();
break;
case "MOVE":
console.log(`[Message] MOVE e=${e.data}`); // ちゃんと動いているようなら消す
if (message["player"] != myPiece) {
makeMove(message["index"], message["player"]);
myTurn = true;
document.getElementById("alert_move").style.display = "inline";
}
break;
default: // ちゃんと動いているようなら消す
console.log(`[Message] (Others) e=${e.data}`);
console.log("No event");
}
};
if (webSock1.readyState == WebSocket.OPEN) {
console.log("Open socket.");
webSock1.onopen();
}
}
//call the connect function at the start.
connect();
// EOF OA16o1o0gA10o0
Step [OA16o1o0gA11o0] 対局申込画面作成 - match_application/ver1o0.html ファイル
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0 # アプリケーションと同名
│ │ └── 📂 match_application
👉 │ │ └── 📄 ver1o0.html
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📄 requirements.txt
<!-- BOF OA16o1o0gA11o0 -->
{% load static %} {# 👈あとで static "URL" を使うので load static します #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tic Tac Toe</title>
<link rel="stylesheet" href='{% static "tic_tac_toe_vol1o0/style/main/ver1o0.css" %}' />
<!-- ================================================
1
1. `src1/apps1/tic_tac_toe_vol1o0/static/tic_tac_toe_vol1o0/style/main/ver1o0.css`
===============================================
-->
</head>
<body>
<div class="wrapper">
<h1>Welcome to Tic Tac Toe Game Copy</h1>
<p>📖 Original: <a href="https://blog.logrocket.com/django-channels-and-websockets/">Django Channels and WebSockets</a></p>
<form method="POST">
{% csrf_token %}
<div class="form-control">
<label for="room">Room id</label>
<input id="room" type="text" name="room_name" required />
</div>
<div class="form-control">
<label for="item_of_my_piece">Your character</label>
<select for="item_of_my_piece" name="my_piece">
<option value="X">X</option>
<option value="O">O</option>
</select>
</div>
<input type="submit" class="button" value="Start Game" />
</form>
</div>
</body>
</html>
<!-- EOF OA16o1o0gA11o0 -->
Step [OA16o1o0gA12o0] 対局画面作成 - playing/ver1o0.html ファイル
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0 # アプリケーションと同名
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
👉 │ │ └── 📄 ver1o0.html
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📄 requirements.txt
<!-- BOF OA16o1o0gA12o0 -->
{% load static %} {# 👈あとで static "URL" を使うので load static します #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tic Tac Toe</title>
<link rel="stylesheet" href='{% static "tic_tac_toe_vol1o0/style/main/ver1o0.css" %}' />
<!-- ================================================
1
1. `src1/apps1/tic_tac_toe/static/tic_tac_toe_vol1o0/style/main/ver1o0.css`
===============================================
-->
</head>
<body>
<div class="wrapper">
<div class="head">
<h1>TIC TAC TOE</h1>
<h3>Welcome to room_{{room_name}}</h3>
</div>
<div id="board" room_name="{{room_name}}" my_piece="{{my_piece}}">
<div class="square" square="0"></div>
<div class="square" square="1"></div>
<div class="square" square="2"></div>
<div class="square" square="3"></div>
<div class="square" square="4"></div>
<div class="square" square="5"></div>
<div class="square" square="6"></div>
<div class="square" square="7"></div>
<div class="square" square="8"></div>
</div>
<div id="alert_move">Your turn. Place your move <strong>{{my_piece}}</strong></div>
</div>
<script src="{% static 'tic_tac_toe_vol1o0/scripts/play/ver1o0.js' %}"></script>
<!-- =================================================
1
1. `src1/apps1/tic_tac_toe_vol1o0/static/tic_tac_toe_vol1o0/scripts/play/ver1o0.js`
================================================
-->
{% block javascript %} {% endblock javascript %}
</body>
</html>
<!-- EOF OA16o1o0gA12o0 -->
Step [OA16o1o0gA13o0] ビュー作成 - match_application/v1o0 フォルダー
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0 # アプリケーションと同名
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
│ │ └── 📄 ver1o0.html
│ ├── 📂 views
│ │ └── 📂 match_application
│ │ └── 📂 ver1o0
👉 │ │ └── 📄 __init__.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📄 requirements.txt
# BOF [OA16o1o0gA13o0]
class MatchApplicationView():
"""[OA16o1o0gA13o0] 対局申込ビュー"""
playing_web_path = "/tic-tac-toe/vol1.0/playing/ver1.0/{0}/?&mypiece={1}"
# ^one
# ----------------------------------------------------
# 1
# 1. `http://example.com:8000/tic-tac-toe/vol1.0/playing/ver1.0/Elephant/?&mypiece=X`
# -------------------------------------------------------
template_path = "tic_tac_toe_vol1o0/match_application/ver1o0.html"
# ^one
# ------------------------------------------------
# 1
# 1. src1/apps1/tic_tac_toe_vol1o0/templates/tic_tac_toe_vol1o0/match_application/ver1o0.html
# ------------------------------------------------
def render(request):
"""描画"""
# 以下のファイルはあとで作ります
from .v_render import render_match_application
# --------- ------------------------
# 1 2
# 1. `src1/apps1/tic_tac_toe_vol1o0/views/match_application/v1o0/v_render.py`
# --------
# 2. `1.` に含まれる関数
return render_match_application(
request,
MatchApplicationView.playing_web_path,
MatchApplicationView.template_path)
# EOF [OA16o1o0gA13o0]
Step [OA16o1o0gA14o0] ビュー作成 - match_application/v1o0/v_render.py ファイル
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0 # アプリケーションと同名
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
│ │ └── 📄 ver1o0.html
│ ├── 📂 views
│ │ └── 📂 match_application
│ │ └── 📂 ver1o0
│ │ ├── 📄 __init__.py
👉 │ │ └── 📄 v_render.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📄 requirements.txt
# BOF OA16o1o0gA14o0
from django.shortcuts import render, redirect
def render_match_application(request, upf_playing, match_application_tp):
"""OA16o1o0gA14o0 描画 - 対局申込
Parameters
----------
upf_playing : str
Url Path Format
match_application_tp : str
Template path
"""
if request.method == "POST":
# 送信後
room_name = request.POST.get("room_name")
myPiece = request.POST.get("my_piece")
# TODO バリデーションチェックしたい
return redirect(upf_playing.format(room_name, myPiece))
# 訪問後
return render(request, match_application_tp, {})
# EOF OA16o1o0gA14o0
Step [OA16o1o0gA15o0] ビュー作成 - playing/v1o0 フォルダー
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0 # アプリケーションと同名
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
│ │ └── 📄 ver1o0.html
│ ├── 📂 views
│ │ ├── 📂 match_application
│ │ │ └── 📂 ver1o0
│ │ │ ├── 📄 __init__.py
│ │ │ └── 📄 v_render.py
│ │ └── 📂 playing
│ │ └── 📂 ver1o0
👉 │ │ └── 📄 __init__.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📄 requirements.txt
# BOF [OA16o1o0gA15o0]
class PlayingView():
"""[OA16o1o0gA15o0] 対局中ビュー"""
template_path = "tic_tac_toe_vol1o0/playing/ver1o0.html"
# ^one
# --------------------------------------
# 1
# 1. `src1/apps1/tic_tac_toe_vol1o0/templates/tic_tac_toe_vol1o0/playing/ver1o0.html`
# --------------------------------------
def render(request, room_name):
"""描画"""
# 以下のファイルはあとで作ります
from .v_render import render_playing
# --------- --------------
# 1 2
# 1. `src1/apps1/tic_tac_toe_vol1o0/views/playing/v1o0/v_render.py`
# --------
# 2. `1.` に含まれる関数
return render_playing(request, room_name, PlayingView.template_path)
# EOF [OA16o1o0gA15o0]
Step [OA16o1o0gA16o0] ビュー作成 - playing/v1o0/v_render.py ファイル
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
│ │ └── 📄 ver1o0.html
│ ├── 📂 views
│ │ ├── 📂 match_application
│ │ │ └── 📂 ver1o0
│ │ │ ├── 📄 __init__.py
│ │ │ └── 📄 v_render.py
│ │ └── 📂 playing
│ │ └── 📂 ver1o0
│ │ ├── 📄 __init__.py
👉 │ │ └── 📄 v_render.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
└── 📄 requirements.txt
# BOF OA16o1o0gA16o0
from django.http import Http404
from django.shortcuts import render
def render_playing(request, room_name, playing_tp):
"""OA16o1o0gA16o0 描画 - 対局
Parameters
----------
playing_tp : str
Template path
"""
myPiece = request.GET.get("mypiece")
if myPiece not in ['X', 'O']:
raise Http404(f"My piece '{myPiece}' does not exists")
context = {
"my_piece": myPiece,
"room_name": room_name
}
return render(request, playing_tp, context)
# EOF OA16o1o0gA16o0
Step [OA16o1o0gA17o0]
Merged to OA16o1o0gA17o1o0
Step [OA16o1o0gA17o1o0] ルート編集 - urls.csv ファイル
👇 以下の既存ファイルの末尾に追記してほしい
├── 📂 src1
│ ├── 📂 apps1
│ │ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ │ ├── 📂 migrations
│ │ │ └── 📄 __init__.py
│ │ ├── 📂 static
│ │ │ └── 📂 tic_tac_toe_vol1o0
│ │ │ ├── 📂 style
│ │ │ │ └── 📂 main
│ │ │ │ └── 📄 ver1o0.css
│ │ │ └── 📂 scripts
│ │ │ └── 📂 play
│ │ │ └── 📄 ver1o0.js
│ │ ├── 📂 templates
│ │ │ └── 📂 tic_tac_toe_vol1o0
│ │ │ ├── 📂 match_application
│ │ │ │ └── 📄 ver1o0.html
│ │ │ └── 📂 playing
│ │ │ └── 📄 ver1o0.html
│ │ ├── 📂 views
│ │ │ ├── 📂 match_application
│ │ │ │ └── 📂 ver1o0
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ └── 📄 v_render.py
│ │ │ └── 📂 playing
│ │ │ └── 📂 ver1o0
│ │ │ ├── 📄 __init__.py
│ │ │ └── 📄 v_render.py
│ │ ├── 📄 __init__.py
│ │ ├── 📄 admin.py
│ │ ├── 📄 apps.py
│ │ └── 📄 tests.py
│ └── 📄 requirements.txt
└── 📂 src1_meta
└── 📂 data
👉 └── 📄 urls.csv
...略... file,path,name,comment,module,class,alias,method
...略...
../src1/project1/urls_tic_tac_toe_vol1o0_autogen.py,tic-tac-toe/vol1.0/match-application/ver1.0/,,"OA16o1o0gA17o1o0 〇×ゲーム1.0巻 対局申込中1.0版",apps1.tic_tac_toe_vol1o0.views.match_application.ver1o0,MatchApplicationView,,render
../src1/project1/urls_tic_tac_toe_vol1o0_autogen.py,tic-tac-toe/vol1.0/playing/ver1.0/<str:room_name>/,,"OA16o1o0gA17o1o0 〇×ゲーム1.0巻 対局中1.0版",apps1.tic_tac_toe_vol1o0.views.playing.ver1o0,PlayingView,,render
Step [OA16o1o0gA17o2o0] ルート編集 - コマンド打鍵
👇 以下のコマンドを打鍵してほしい
cd ../src1_meta
python -m scripts.auto_generators.urls
cd ../src1
docker-compose restart
- ディレクトリーは、がんばって移動してほしい
- スクリプトについて See also: O3o2o_1o0g2o0
- 設定ファイルを変更したら、サーバーの再起動が必要
Step [OA16o1o0gA18o0]
Merged to OA16o1o0gA17o1o0
Step [OA16o1o0gA19o0] consumer/ver1o0.py ファイルの作成
以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
│ │ └── 📄 ver1o0.html
│ ├── 📂 views
│ │ ├── 📂 match_application
│ │ │ └── 📂 ver1o0
│ │ │ ├── 📄 __init__.py
│ │ │ └── 📄 v_render.py
│ │ └── 📂 playing
│ │ └── 📂 ver1o0
│ │ ├── 📄 __init__.py
│ │ └── 📄 v_render.py
│ ├── 📂 websocks
│ │ └── 📂 consumer
👉 │ │ └── 📄 ver1o0.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
├── 📂 project1 # プロジェクト
│ ├── 📄 urls_tic_tac_toe_v1.py
│ └── 📄 urls.py
└── 📄 requirements.txt
# See also: 📖[Django Channels and WebSockets](https://blog.logrocket.com/django-channels-and-websockets/)
import json
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class TicTacToeV1Consumer(AsyncJsonWebsocketConsumer):
"""OA16o1o0gA19o0 非同期のWebソケットのコンシューマー"""
async def connect(self):
"""接続時"""
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'room_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
"""切断時"""
print("Disconnected")
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive(self, text_data):
"""メッセージ受信時
Receive message from WebSocket.
Get the event and send the appropriate event
"""
print(
f"[Debug] Consumer1 receive text_data={text_data}") # ちゃんと動いているようなら消す
response = json.loads(text_data)
event = response.get("event", None)
message = response.get("message", None)
if event == 'MOVE':
# Send message to room group
await self.channel_layer.group_send(self.room_group_name, {
'type': 'send_message', # type属性は必須
'message': message,
"event": "MOVE"
})
if event == 'START':
# Send message to room group
await self.channel_layer.group_send(self.room_group_name, {
'type': 'send_message', # type属性は必須
'message': message,
'event': "START"
})
if event == 'END':
# Send message to room group
await self.channel_layer.group_send(self.room_group_name, {
'type': 'send_message', # type属性は必須
'message': message,
'event': "END"
})
async def send_message(self, res):
"""メッセージ送信時
Receive message from room group
"""
# Send message to WebSocket
await self.send(text_data=json.dumps({
"payload": res,
}))
Step [OA16o1o0gA20o0] Webソケット用ルート新規作成 - ws_urls_tic_tac_toe_v1.py ファイル
👇 以下のファイルを新規作成してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
│ │ └── 📄 ver1o0.html
│ ├── 📂 views
│ │ ├── 📂 match_application
│ │ │ └── 📂 ver1o0
│ │ │ ├── 📄 __init__.py
│ │ │ └── 📄 v_render.py
│ │ └── 📂 playing
│ │ └── 📂 ver1o0
│ │ ├── 📄 __init__.py
│ │ └── 📄 v_render.py
│ ├── 📂 websocks
│ │ └── 📂 consumer
│ │ └── 📄 ver1o0.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
├── 📂 project1 # プロジェクト
│ ├── 📄 urls_tic_tac_toe_v1.py
│ ├── 📄 urls.py
👉 │ └── 📄 ws_urls_tic_tac_toe_v1.py
└── 📄 requirements.txt
# BOF OA16o1o0gA20o0
# See also: 📖 [Channels - Consumers](https://channels.readthedocs.io/en/latest/topics/consumers.html)
from django.conf.urls import url
# OA16o1o0gA20o0 〇×ゲーム1.0巻 1.0版
from apps1.tic_tac_toe_vol1o0.websocks.consumer.ver1o0 import TicTacToeV1Consumer
# ------------------ ------ -------------------
# 11 12 2
# -------------------------------------------------
# 10
# 10, 12. ディレクトリー
# 11. アプリケーション
# 2. `12.` に含まれる __init__.py ファイルにさらに含まれるクラス
websocket_urlpatterns = [
# OA16o1o0gA20o0 〇×ゲーム1.0巻 1.0版
url(r'^tic-tac-toe/v1/playing/(?P<room_name>\w+)/$',
# --------------------------------------------
# 1
TicTacToeV1Consumer.as_asgi()),
# -----------------------------
# 2
# 1. 例えば `ws://example.com/tic-tac-toe/v1/playing/Elephant/` のようなURLのパスの部分
# --------------------------------
# room_name は変数として渡される
# 2. クラス名とメソッド。 URL を ASGI形式にする
]
# EOF OA16o1o0gA20o0
Step [OA16o1o0gA21o0] Webソケット用総合ルート編集 - asgi.py ファイル<その2>
👇以下の既存のファイルを編集してほしい
├── 📂 src1
│ ├── 📂 apps1
│ │ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ │ ├── 📂 migrations
│ │ │ └── 📄 __init__.py
│ │ ├── 📂 static
│ │ │ └── 📂 tic_tac_toe_vol1o0
│ │ │ ├── 📂 style
│ │ │ │ └── 📂 main
│ │ │ │ └── 📄 ver1o0.css
│ │ │ └── 📂 scripts
│ │ │ └── 📂 play
│ │ │ └── 📄 ver1o0.js
│ │ ├── 📂 templates
│ │ │ └── 📂 tic_tac_toe_vol1o0
│ │ │ ├── 📂 match_application
│ │ │ │ └── 📄 ver1o0.html
│ │ │ └── 📂 playing
│ │ │ └── 📄 ver1o0.html
│ │ ├── 📂 views
│ │ │ ├── 📂 match_application
│ │ │ │ └── 📂 ver1o0
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ └── 📄 v_render.py
│ │ │ └── 📂 playing
│ │ │ └── 📂 ver1o0
│ │ │ ├── 📄 __init__.py
│ │ │ └── 📄 v_render.py
│ │ ├── 📂 websocks
│ │ │ └── 📂 consumer
│ │ │ └── 📄 ver1o0.py
│ │ ├── 📄 __init__.py
│ │ ├── 📄 admin.py
│ │ ├── 📄 apps.py
│ │ └── 📄 tests.py
│ ├── 📂 project1 # プロジェクト
👉 │ │ ├── 📄 asgi.py
│ │ ├── 📄 urls_tic_tac_toe_v1.py
│ │ ├── 📄 urls.py
│ │ └── 📄 ws_urls_tic_tac_toe_v1.py
│ └── 📄 requirements.txt
├── 📂 src1_meta
│ ├── 📂 data
│ │ └── 📄 urls.csv
│ └── 📂 scripts
│ └── 📂 auto_generators
│ └── 📄 urls.py
└── 📄 .gitignore
# ...略...
# * 以下を追加
# OA16o1o0gA21o0 〇×ゲーム v1
from . import ws_urls_tic_tac_toe_v1
# - ----------------------
# 1 2
# 1. 同じディレクトリー
# 2. `src1/projectN/ws_urls_tic_tac_toe_v1.py`
# ----------------------
# ...略...
# この辺に os.environ.setdefault(...)
# ...略...
# OA15o1o0g9o0 複数のアプリケーションの websocket_urlpatterns をマージします
websocket_urlpatterns_merged = []
# ...略...
# * 以下を追加
# OA16o1o0gA21o0 〇×ゲーム v1
websocket_urlpatterns_merged.extend(
ws_urls_tic_tac_toe_v1.websocket_urlpatterns)
Step [OA16o1o0gA22o0] Djangoの設定 - settings.py ファイル
👇 以下の既存のファイルを編集してほしい
└── 📂 src1
├── 📂 apps1
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
│ │ └── 📄 ver1o0.html
│ ├── 📂 views
│ │ ├── 📂 match_application
│ │ │ └── 📂 ver1o0
│ │ │ ├── 📄 __init__.py
│ │ │ └── 📄 v_render.py
│ │ └── 📂 playing
│ │ └── 📂 ver1o0
│ │ ├── 📄 __init__.py
│ │ └── 📄 v_render.py
│ ├── 📂 websocks
│ │ └── 📂 consumer
│ │ └── 📄 ver1o0.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
├── 📂 project1 # プロジェクト
│ ├── 📄 asgi.py
👉 │ ├── 📄 settings.py
│ ├── 📄 urls_tic_tac_toe_v1.py
│ ├── 📄 urls.py
│ └── 📄 ws_urls_tic_tac_toe_v1.py
└── 📄 requirements.txt
# ...略...
TEMPLATES = [
{
# ...略...
'DIRS': [
# ...略...
# * 以下を追加
# OA16o1o0gA22o0 〇×ゲーム1.0巻
os.path.join(BASE_DIR, 'apps1/tic_tac_toe_vol1o0/templates'),
# ----------------------------------
# 10
#
# Example: `/src1/apps1/tic_tac_toe_vol1o0/templates/tic_tac_toe_vol1o0/match_application/ver1o0.html`
# ------------------ -------------------
# 11 2
# ----------------------------------
# 10
# 10. テンプレート ディレクトリーへのパス
# 11. アプリケーション
# 2. まるで `http://example.com/tic_tac_toe_vol1o0` という素材フォルダーがあるかのように扱われる
# --------------------
],
# ...略...
},
]
Step [OA16o1o0gA23o0] Web画面へアクセス
このゲームは2人用なので、Webページを2窓で開き、片方が X プレイヤー、もう片方が O プレイヤーとして遊んでください
📖 http://localhost:8000/tic-tac-toe/vol1.0/match-application/ver1.0/
Step [OA16o1o0gA24o0] ランチャーのリンク用データ追加 - finished-lessons.csv ファイル
👇 以下の既存ファイルの最終行に追記してほしい
└── 📂 src1
├── 📂 apps1
│ ├── 📂 portal_v1 # アプリケーション
│ │ └── 📂 data
👉 │ │ └── 📄 finished-lessons.csv
│ └── 📂 tic_tac_toe_vol1o0 # アプリケーション
│ ├── 📂 migrations
│ │ └── 📄 __init__.py
│ ├── 📂 static
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 style
│ │ │ └── 📂 main
│ │ │ └── 📄 ver1o0.css
│ │ └── 📂 scripts
│ │ └── 📂 play
│ │ └── 📄 ver1o0.js
│ ├── 📂 templates
│ │ └── 📂 tic_tac_toe_vol1o0
│ │ ├── 📂 match_application
│ │ │ └── 📄 ver1o0.html
│ │ └── 📂 playing
│ │ └── 📄 ver1o0.html
│ ├── 📂 views
│ │ ├── 📂 match_application
│ │ │ └── 📂 ver1o0
│ │ │ ├── 📄 __init__.py
│ │ │ └── 📄 v_render.py
│ │ └── 📂 playing
│ │ └── 📂 ver1o0
│ │ ├── 📄 __init__.py
│ │ └── 📄 v_render.py
│ ├── 📂 websocks
│ │ └── 📂 consumer
│ │ └── 📄 ver1o0.py
│ ├── 📄 __init__.py
│ ├── 📄 admin.py
│ ├── 📄 apps.py
│ └── 📄 tests.py
├── 📂 project1 # プロジェクト
│ ├── 📄 urls_tic_tac_toe_v1.py
│ ├── 📄 urls.py
│ └── 📄 ws_urls_tic_tac_toe_v1.py
└── 📄 requirements.txt
👇 冗長なスペース,冗長なダブルクォーテーション,末尾のカンマ は止めてほしい
/tic-tac-toe/vol1.0/match-application/ver1.0/,OA16o1o0gA24o0 〇×ゲーム1.0巻 対局申込中1.0版
👇 ランチャーにリンクが追加されていることを確認してほしい
次の記事
📖 DockerでTic-Tac-Toeの思考エンジンを作ろう!
参考にした記事
Web Socket
📖 Django Channels and WebSockets