1
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 3 years have passed since last update.

Python+Cursesで、大昔のキャラクタゲームを再現してみよう。その2

Last updated at Posted at 2019-12-05

昔懐かし、Robotsゲームを、python3とcursesで再現してみました。
私の投稿のCから、pythonへの移植版です。
元の記事は、これです→【昔懐かし、CBM-3032のようなキャラクタゲームをncurseswで再現しやう。】
まだ、pythonには慣れてないので、ちょっと手古摺りましたが、何かのご参考になれば幸いです。

このゲームは、途中で、Ctrl-Cで止めると、ターミナルの設定がおかしくなってしまうので、
ゲーム更新時の、"Try Again? [y/n]" で、'n'を押して止めて下さい。

Robotsゲームですが、名前を変えて、「maneaters」にしてあります。
拙いプログラムですが、よろしくお願いします。

maneaters.py
#!/usr/bin/python3

import curses
import random
import locale

xsize = 40  # ゲーム画面サイズx
ysize = 23  # ゲーム画面サイズy
gamestat = 0  # ゲームの状態 0:プレイ中 1:負け 3:勝ち
stdscr = curses.initscr()
CRASH = ""
SPACE = " "


def putchara(x, y, str):
    #
    # 画面に文字を表示する関数。
    # 全角単位で扱っているので、
    # x座標を2倍しています。
    #
    stdscr.addstr(int(y), int(x*2), str)


class enemy:
    max = 12        # 最初の敵の数
    l = [0]*40  # 敵が生きているかのフラグ
    x = [0]*40  # 敵x座標
    y = [0]*40  # 敵y座標
    c = ""  # 敵キャラクタ

    def no():
        global gamestat
        f = 0
        for j in range(enemy.max):
            f += enemy.l[j]
        if f == 0:
            gamestat = 3
        return

    def move():
        dx = 0
        dy = 0
        tx = 0
        ty = 0

        for i in range(enemy.max):
            if enemy.l[i] == 0:  # 敵が死んでいたら、スキップ
                continue

            # プレイヤーを追いかけるように差分を求める
            dx = 0
            dy = 0
            if enemy.x[i] < player.x:
                dx = 1
            if enemy.x[i] > player.x:
                dx = -1
            if enemy.y[i] < player.y:
                dy = 1
            if enemy.y[i] > player.y:
                dy = -1

            #  今の位置の敵の姿を消す
            putchara(enemy.x[i], enemy.y[i], SPACE)

            #  敵の動いた先の座標を求める
            tx = enemy.x[i]+dx
            ty = enemy.y[i]+dy

            #
            #  敵同士の衝突のチェック
            #
            for j in range(enemy.max):
                if (i != j and enemy.l[j] == 1 and tx == enemy.x[j] and ty == enemy.y[j]):
                    enemy.l[i] = 0     # 敵が生きているフラグを降ろす。

            #
            #  岩との衝突チェック
            #
            for j in range(rock.max):
                if (rock.l[j] == 1 and tx == rock.x[j] and ty == rock.y[j]):
                    rock.l[j] = 0
                    enemy.l[i] = 0
                    putchara(tx, ty, SPACE)  # 岩、敵を消す

            enemy.x[i] = tx
            enemy.y[i] = ty
            if enemy.l[i] != 0:  # 生きているか?
                putchara(tx, ty, enemy.c)  # 敵を描画


class rock:
    max = 120     # 最初の岩の数
    l = [0]*320  # 岩があるかどうかのフラグ
    x = [0]*320  # 岩x座標
    y = [0]*320  # 岩y座標
    c = ""  # 岩キャラクタ


class player:
    x = 0         # プレイヤーx座標
    y = 0         # プレイヤーy座標
    c = ""      # プレイヤーキャラクタ

    def check_crash():
        global gamestat
        #
        # プレイヤーが何かに当たったかどうかの判断
        #
        for i in range(enemy.max):
            if (enemy.l[i] == 1 and player.x == enemy.x[i] and player.y == enemy.y[i]):
                gamestat = 1
        for i in range(rock.max):
            if (rock.l[i] == 1 and player.x == rock.x[i] and player.y == rock.y[i]):
                gamestat = 1
        return

    def move():
        while True:
            ch = stdscr.getkey()
            if ch == '7':
                dx = -1
                dy = -1
                break
            elif ch == '8':
                dx = 0
                dy = -1
                break
            elif ch == '9':
                dx = 1
                dy = -1
                break
            elif ch == 'u' or ch == '4':
                dx = -1
                dy = 0
                break
            elif ch == 'i' or ch == '5':
                dx = 0
                dy = 0
                break
            elif ch == 'o' or ch == '6':
                dx = 1
                dy = 0
                break
            elif ch == 'j' or ch == '1':
                dx = -1
                dy = 1
                break
            elif ch == 'k' or ch == '2':
                dx = 0
                dy = 1
                break
            elif ch == 'l' or ch == '3':
                dx = 1
                dy = 1
                break

    # プレイヤーが画面からはみ出ないか?
        px = player.x+dx
        py = player.y+dy
        if (px >= 0) and (px < xsize) and (py >= 0) and (py < ysize):
            putchara(player.x, player.y, SPACE)  # プレイヤーを一歩進ませる。
            player.x = px
            player.y = py
            putchara(px, py, player.c)


class game:
    #
    # ゲームの説明画面
    #
    def instruction():
        stdscr.clear()      # 画面のクリア

        stdscr.addstr("Maneaters Ver 1.3\n")
        stdscr.addstr("Mission : kill all maneaters to survive!\n")
        stdscr.addstr("O -- Maneater, chase player step by step.\n")
        stdscr.addstr("# -- Rock, die maneaters and player when touched. \n")
        stdscr.addstr("@ -- Player, control for maneaters to crash to rock and survive!\n")
        stdscr.addstr("\n")
        stdscr.addstr("Key control:             Tenkey:     \n")
        stdscr.addstr(" 7  8  9            7 8  9   \n")
        stdscr.addstr("  ↖ ↑  ↗                 ↖ ↑  ↗     \n")
        stdscr.addstr("u← i →o             4← 5→ 6   \n")
        stdscr.addstr("  ↙ ↓  ↘                 ↙ ↓  ↘     \n")
        stdscr.addstr(" j  k   l           1  2  3   \n")
        stdscr.addstr("\n")
        stdscr.addstr(" 'i' and '5' move maneaters and don't move player\n")
        stdscr.addstr("             Good Luck\n")
        stdscr.addstr("hit key\n")

        stdscr.getch()  # 一文字キー入力待ち。

    #
    # 初期化
    #
    def init():
        global gamestat
        stdscr.clear()        # 画面クリア

        gamestat = 0         # ゲーム状態を初期化

    #
    #  岩を置く。
    #  0番目はプレイヤーと敵との衝突回避のため、
    #  ダミーを画面中心に置く。そのため、岩の個数は、
    #  max-1となる。
    #
        for i in range(rock.max):
            a = random.randint(0, xsize-1)
            b = random.randint(0, ysize-1)
            rock.x[i] = a
            rock.y[i] = b
            putchara(a, b, rock.c)
            rock.l[i] = 1
        rock.x[0] = int(xsize/2)
        rock.y[0] = int(ysize/2)
        rock.l[0] = 0

    #  敵を岩とぶつからないように置く。
        i = 0
        while i < enemy.max:
            f = 0
            ex = random.randint(0, xsize-1)
            ey = random.randint(0, ysize-1)
            for j in range(rock.max):
                if (ex == rock.x[j] and ey == rock.y[j]):
                    f = f+1
            if f == 0:
                enemy.x[i] = ex
                enemy.y[i] = ey
                putchara(ex, ey, enemy.c)
                enemy.l[i] = 1
                i += 1

    # プレイヤーを画面中心に置く。
        player.x = xsize/2
        player.y = int(ysize/2)
        putchara(player.x, player.y, player.c)

    #
    #  ゲームの説明画面とメインのループ
    #
    def play():
        while True:
            game.instruction()

            game.init()  # ゲームの初期化

            game.main()  # ゲームのメイン処理

            if gamestat == 0:
                continue

            if gamestat == 1:  # プレイヤー負け
                putchara(player.x, player.y, CRASH)
                stdscr.addstr(0, 0, "You Lose. Game Over.")

            if gamestat == 3:  # プレイヤー勝ち
                stdscr.addstr(0, 0, "You Win! Game Over.")

            stdscr.refresh()
            if game.tryagainp() == 0:
                return   # リプレイしなければリターン。

    #
    #  ゲームのメイン処理
    #
    def main():
        while True:
            stdscr.refresh()

            player.move()
            player.check_crash()
            if gamestat != 0:
                return

            enemy.move()
            player.check_crash()
            if gamestat != 0:
                return

            enemy.no()
            if gamestat != 0:
                return

    def tryagainp():
        stdscr.addstr(1, 0, "Try Again? [y/n]")
        while True:
            ch = stdscr.getkey()
            if ch == 'n':
                return(0)
            if ch == 'y':
                return(1)


locale.setlocale(locale.LC_ALL, '')  # curses で、UTF-8 を使うために、
                                     # LC_ALLをセットする。

curses.noecho()  #  キーボードエコーをなしにする
curses.curs_set(0)
game.play()             # ゲームを走らせる。
curses.endwin()         # 画面を閉じる。
exit(0)  # 終了。

コンパイル

pyinstallerでコンパイルできます。以下のようなメッセージがでてきて、./dist以下にバイナリが出来ます。"maneaters"が実行ファイルです。

$ pyinstaller maneaters.py
511 INFO: PyInstaller: 3.6
511 INFO: Python: 3.8.2
559 INFO: Platform: Linux-5.4.0-47-generic-x86_64-with-glibc2.29
559 INFO: wrote /home/gar/maneaters/maneaters.spec
600 INFO: UPX is not available.
615 INFO: Extending PYTHONPATH with paths
['/home/gar/maneaters', '/home/gar/maneaters']
615 INFO: checking Analysis
615 INFO: Building Analysis because Analysis-00.toc is non existent
615 INFO: Initializing module dependency graph...
654 INFO: Caching module graph hooks...
678 INFO: Analyzing base_library.zip ...
5981 INFO: Processing pre-find module path hook   distutils
6009 INFO: distutils: retargeting to non-venv dir '/usr/lib/python3.8'
11391 INFO: Caching module dependency graph...
11574 INFO: running Analysis Analysis-00.toc
11624 INFO: Analyzing /home/gar/maneaters/maneaters.py
11843 INFO: Processing module hooks...
11843 INFO: Loading module hook "hook-_tkinter.py"...
12464 INFO: checking Tree
12464 INFO: Building Tree because Tree-00.toc is non existent
12464 INFO: Building Tree Tree-00.toc
12483 INFO: checking Tree
12483 INFO: Building Tree because Tree-01.toc is non existent
12483 INFO: Building Tree Tree-01.toc
12491 INFO: Loading module hook "hook-xml.etree.cElementTree.py"...
12520 INFO: Loading module hook "hook-sysconfig.py"...
12608 INFO: Loading module hook "hook-lib2to3.py"...
12635 INFO: Loading module hook "hook-pydoc.py"...
12650 INFO: Loading module hook "hook-distutils.py"...
12654 INFO: Loading module hook "hook-encodings.py"...
12731 INFO: Loading module hook "hook-xml.py"...
12911 INFO: Looking for ctypes DLLs
12964 INFO: Analyzing run-time hooks ...
12970 INFO: Including run-time hook 'pyi_rth__tkinter.py'
12993 INFO: Including run-time hook 'pyi_rth_multiprocessing.py'
13008 INFO: Looking for dynamic libraries
14161 INFO: Looking for eggs
14162 INFO: Python library not in binary dependencies. Doing additional searching...
14576 INFO: Using Python library /usr/lib/x86_64-linux-gnu/libpython3.8.so.1.0
14587 INFO: Warnings written to /home/gar/maneaters/build/maneaters/warn-maneaters.txt
14645 INFO: Graph cross-reference written to /home/gar/maneaters/build/maneaters/xref-maneaters.html
14680 INFO: checking PYZ
14680 INFO: Building PYZ because PYZ-00.toc is non existent
14680 INFO: Building PYZ (ZlibArchive) /home/gar/maneaters/build/maneaters/PYZ-00.pyz
15378 INFO: Building PYZ (ZlibArchive) /home/gar/maneaters/build/maneaters/PYZ-00.pyz completed successfully.
15385 INFO: checking PKG
15385 INFO: Building PKG because PKG-00.toc is non existent
15385 INFO: Building PKG (CArchive) PKG-00.pkg
15508 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
15509 INFO: Bootloader /home/gar/.local/lib/python3.8/site-packages/PyInstaller/bootloader/Linux-64bit/run
15509 INFO: checking EXE
15509 INFO: Building EXE because EXE-00.toc is non existent
15510 INFO: Building EXE from EXE-00.toc
15538 INFO: Appending archive to ELF section in EXE /home/gar/maneaters/build/maneaters/maneaters
15579 INFO: Building EXE from EXE-00.toc completed successfully.
15582 INFO: checking COLLECT
15582 INFO: Building COLLECT because COLLECT-00.toc is non existent
15582 INFO: Building COLLECT COLLECT-00.toc
17116 INFO: Building COLLECT COLLECT-00.toc completed successfully.
$ 
1
2
6

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
1
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?