naritoブログ

【お知らせ】
新ブログができました。今後そちらで更新し、このサイトは更新されません(ウェブサイト自体は残しておきます)
このブログの内容に関してコメントしたい場合は、新ブログのフリースペースに書き込んでください

このブログの内容を新ブログに移行中です。このブログで見つからない記事は、新ブログにありま

Pythonで、1文字ずつ入力を受け取る

約398日前 2017年11月8日14:23
プログラミング関連
Python
CUIでリアルタイムなやりとりをさせようとすると、1文字入力されたら、それをすぐにプログラム側に返したいって場合があります。つまり、エンターをいちいち押さずにキー押下ですぐに反応させたい、ということです。
組み込み関数のinputではできません。
そんな場合のTipsです。


CTRL_C = 3

try:
from msvcrt import getch
except ImportError:
def getch():
import sys
import tty
import termios
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)

while True:
key = ord(getch())
if key == CTRL_C:
break
else:
message = 'input, {0}'.format(chr(key))
print(message)




使ってみると、ちゃんと動作しています。



msvcrtモジュールには文字を1文字ずつ受け取るgetchがあります。
が、このモジュールはWindowsプラットフォームのための機能です。
そこで、Windowsであればそれを使い、そうでなければgetchという関数を改めて作っています。
他人のコードをいくつか見ましたが、おおむね以下のようなコードを皆さん作っていました。

try:
from msvcrt import getch
except ImportError:
def getch():
import sys
import tty
import termios
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)



getch()で返ってくる文字をordで変換しておきます。
CTRL_Cを押された際、これは3になります。CTRL_Cならば、処理終了ということでbreakします。
表示する際はchrで変換するようにしておきます。

CTRL_C = 3
...
...
while True:
key = ord(getch())
if key == CTRL_C:
break
else:
message = 'input, {0}'.format(chr(key))
print(message)




これを応用し、ちょっとしたハイ&ローめいたゲームを作ります。
エンターをいちいち入力しないので、リアルタイム感が得られるでしょう。


ソースコード

import random
import subprocess
import sys
import time

# コンソールをクリアするコマンドがプラットフォームによって違う
# windowsはcls、ほかは大体clear
# getch関数も、Windowsじゃなければ定義
if sys.platform == 'win32':
clear_command = 'cls'
from msvcrt import getch
else:
clear_command = 'clear'
def getch():
import tty
import termios
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)

CTRL_C = 3
HIGH = 49
LAW = 50

GAME_TEMPLATE = """\
勝ち{0[0]}:負け{0[1]}

相手:{1} あなた:{2}

相手より...{3}
1. 高い
2. 低い
"""

CARDS = [
'A', '2', '3', '4', '5', '6', '7', '8', '9',
'10', 'J', 'Q', 'K', 'ジョーカー'
]

# 勝ち, 負け
score = [0, 0]

# 相手の出したカードの履歴
history = []


def update(key):
"""勝敗の決定、結果の表示、次ゲームへの移行を行う"""
# 出力をクリア
subprocess.run([clear_command], shell=True)

# 勝敗を決定する
player = random.choice(CARDS)
enemy = history[-1]
result = ''
your_select = ''
if key == HIGH:
your_select = 'High'
if CARDS.index(player) > CARDS.index(enemy):
score[0] += 1
result = 'Win'
else:
score[1] += 1
result = 'Lose'

elif key == LAW:
your_select = 'Law'
if CARDS.index(player) < CARDS.index(enemy):
score[0] += 1
result = 'Win'
else:
score[1] += 1
result = 'Lose'

# 勝敗の表示
message = GAME_TEMPLATE.format(score, enemy, player, your_select)
print(message)
print(result)
time.sleep(3)

# 次ゲームへ移行
card_set()


def card_set():
"""敵がカードをセットし、その状態を表示する"""
# 出力をクリア
subprocess.run([clear_command], shell=True)

# 敵カードを決め、履歴に追加し、そのカードを画面に表示
enemy = random.choice(CARDS)
history.append(enemy)
message = GAME_TEMPLATE.format(score, enemy, '?', '')
print(message)


def main():
"""ゲームの開始"""
card_set()
while True:
key = ord(getch())
if key == CTRL_C:
break
elif key == HIGH or key == LAW:
update(key)


if __name__ == '__main__':
main()



まるでゲーム画面の一部が更新されているように見えますが、実際はそんなことありません。
画面の内容を消去し、新たに表示し直すことで、そう見せているだけです。


Windowsのコマンドプロンプトならば「cls」で消去できますし、
それ以外ならば概ね「clear」でそれが可能です。
その部分だけをするならば、以下のようにできます。ソースコードでは、これに加えてmsvcrtやgetch関数の定義等もしていますね。

if sys.platform == 'win32':
clear_command = 'cls'
else:
clear_command = 'clear'



これだけで結構面白いことができるのですが、もっと面白いCUIアプリケーションも作れます。
興味があれば、cursesモジュールも調べてみましょう。