3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

DjangoとDocker練習OA16o1o0 Webブラウザ越しに2人対戦できる〇×ゲームを作ろう!

Last updated at Posted at 2022-03-21

サンプルを見る

📖 この記事のゴール

目標

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版

👇 ランチャーにリンクが追加されていることを確認してほしい

📖 http://localhost:8000/

次の記事

📖 DockerでTic-Tac-Toeの思考エンジンを作ろう!

参考にした記事

Web Socket

📖 Django Channels and WebSockets

Django settings

📖 スタティックファイルの利用

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?