pythonでCLIで動くPongを作ってみた

f:id:cacapon:20211203170247p:plain

こんにちは、cacaponです😄

今日は、CLI上で動くPongを作ったので

その時の話をしようかと思います✨

そもそもPongって何?

1972年にアタリ社が発売したゲームで

端的に言えばエアホッケーみたいなゲームです*1

一般的には一番最初のテレビゲームとして普及したようです

CLIってなんぞや?

Comand Line Interface の略で

コマンドプロンプトBash,PowerShellMacのコンソールのように

文字入力で動かすインターフェースの事を指します CUI*2とも言います

対となすものとしてGUIがあります。

Windowsとかのクリックとか、スマホのアプリをタップで開くなんかもGUIですね。


通常のCLIのメリットとしては、コマンドの組み合わせで複雑な処理を一括で行えることが挙げられます*3

それゆえ、プログラマーやコアなパソコンユーザーの方はCLIを好む方も多いです。

ゲームの場合だと、実装がめんどくさいグラフィカルな部分を簡単な文字出力で実現できるので

基本的なゲームを作るのにはとても向いていると私は考えています😄

pythonでつくったPongはこちら

f:id:cacapon:20211203173145g:plain

ちょっとちらつきが気になりますかね…😅

どんな感じで作ったの?

元々はUE4でPongを作ろうと思っていたのですが、

UE4自体の知見があまり多くは無い状態でしたので、

とりあえず慣れているPythonでコードベースで作ってみようと思ったのがきっかけです。


作り方としては、必要な素材をリストアップしました。
今回は以下のような感じ🙄

  • ステージ
  • ボール
  • 得点
  • パドル

あとは、行き当たりばったりってとりあえず動くように 一個ずつ実装してきました。

ソースコード

折りたたんでいます

import os
import msvcrt
import random

STAGE_HEIGHT = 15
STAGE_WIDTH = 100

STAGE = []

BALL_VEC_TYPE = {
    'UL': [-1, -1],
    'UR': [-1, 1],
    'DL': [1, -1],
    'DR': [1, 1]
}
BALL_VEC = 'UL'
BALL_POS = [STAGE_HEIGHT // 2, STAGE_WIDTH // 2]
POINT = [0, 0]


def init_stage():
    STAGE.append(list('-' * STAGE_WIDTH))
    for i in range(STAGE_HEIGHT - 2):
        STAGE.append(list(' ' * STAGE_WIDTH))
    STAGE.append(list('-' * STAGE_WIDTH))


def set_puddle(pos, is_player):
    x_pos = 1 if is_player else STAGE_WIDTH - 2

    # 一旦|を消す
    for row in STAGE:
        if row[x_pos] == '|':
            row[x_pos] = ' '

    STAGE[pos - 1][x_pos] = '|'
    STAGE[pos][x_pos] = '|'
    STAGE[pos + 1][x_pos] = '|'


def draw_stage():
    os.system('cls') # PowerShellの場合。bashで動かす場合はclearにする
    print(' ' * (STAGE_WIDTH // 2 - 5), POINT, ' ' * (STAGE_WIDTH // 2 - 5))
    for row in STAGE:
        print(''.join(row))


def realtime_input():
    cmd = ['w', 's', 'q']
    if msvcrt.kbhit():  # キーが押されているか
        key = msvcrt.getch().decode()  # 押されていれば、キーを取得する
        return key if key in cmd else ' '
    else:
        return ' '


def puddle_move(key):
    if key == 'w':
        return -1
    if key == 's':
        return 1
    return 0


def AI_puddle_move(pos):
    random_num = random.random()

    if random_num < 0.33:
        return 1
    if random_num < 0.67:
        return 0
    else:
        return -1


def puddle_fix_pos(pos):
    # 1 ~ HEIGHT -1
    if pos <= 1:
        return 2
    if pos >= STAGE_HEIGHT - 2:
        return STAGE_HEIGHT - 3
    return pos


def ball_move():
    # 45°に動く
    # 移動先のパターン
    # ' ' 移動
    # '-' 反射
    # '|' 反射
    # '両端' 得点
    global BALL_VEC
    global BALL_VEC_TYPE
    global BALL_POS
    global POINT

    vec = BALL_VEC_TYPE[BALL_VEC]

    moved_pos = [
        BALL_POS[0] + vec[0],
        BALL_POS[1] + vec[1],
    ]

    if moved_pos[1] == STAGE_WIDTH - 1:
        POINT[0] += 1
        STAGE[BALL_POS[0]][BALL_POS[1]] = ' '
        BALL_POS = [STAGE_HEIGHT // 2, STAGE_WIDTH // 2]
        BALL_VEC = 'UR'
        return
    elif moved_pos[1] == 0:
        POINT[1] += 1
        STAGE[BALL_POS[0]][BALL_POS[1]] = ' '
        BALL_POS = [STAGE_HEIGHT // 2, STAGE_WIDTH // 2]
        BALL_VEC = 'UL'
        return

    if STAGE[moved_pos[0]][moved_pos[1]] == ' ':
        STAGE[BALL_POS[0]][BALL_POS[1]] = ' '
        STAGE[moved_pos[0]][moved_pos[1]] = 'o'
        BALL_POS = moved_pos
        return
    elif STAGE[moved_pos[0]][moved_pos[1]] == '-':
        if BALL_VEC == 'UR':
            BALL_VEC = 'DR'
        elif BALL_VEC == 'UL':
            BALL_VEC = 'DL'
        elif BALL_VEC == 'DR':
            BALL_VEC = 'UR'
        elif BALL_VEC == 'DL':
            BALL_VEC = 'UL'
    elif STAGE[moved_pos[0]][moved_pos[1]] == '|':
        if BALL_VEC == 'UR':
            BALL_VEC = 'UL'
        elif BALL_VEC == 'UL':
            BALL_VEC = 'UR'
        elif BALL_VEC == 'DR':
            BALL_VEC = 'DL'
        elif BALL_VEC == 'DL':
            BALL_VEC = 'DR'
    else:
        Exception('想定外')


def main():
    init_stage()
    puddle_pos_1p = 7
    puddle_pos_2p = 7

    while True:
        draw_stage()
        key = realtime_input()
        if key == 'q':
            break
        # update
        # 1p controller
        puddle_pos_1p += puddle_move(key)
        puddle_pos_1p = puddle_fix_pos(puddle_pos_1p)
        set_puddle(puddle_pos_1p, True)

        # AI controller
        puddle_pos_2p += AI_puddle_move(puddle_pos_2p)
        puddle_pos_2p = puddle_fix_pos(puddle_pos_2p)
        set_puddle(puddle_pos_2p, False)

        # Ball
        ball_move()

    print('Thank you for Playing')


if __name__ == '__main__':
    main()

最後に

実際に作ってみて、どんな要素が必要か分かったかなぁと思います😄
勉強になりました✨

今度はUE4で実装してみようと思いますが、その前にクラスベースで一度作り直した方がいいかも💦

それではまた😆

参考記事

ja.wikipedia.org

*1:ほんとは卓球らしいですけど

*2:Character User Interface の略

*3:例えば指定の階層に一気に移動したり、
複数のファイル名を一括変更したり
あるファイルを読み込んでプログラムを実行するなども出来ます。
最も私もにわかCLI使いなので、私がし倣いもっとすごいことも出来ると思います