0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

点字を使った CUI での2値セル・オートマトン描画

Last updated at Posted at 2024-12-06

きっかけ

点字を使ってぐるぐる表示ができる、ということを調べた結果
「これは2値セル・オートマトンの描画に使えるのでは?」
と思ったので実装してみました。

実装

ただ描画するだけなら点字キャラクタを並べて表示すればいいだけなのですが、セル・オートマトンの各セルの時間的変化を見せたいので curses を使うことにしました。

ユニコードには点字として6点のものと、漢点字とも呼ばれる8点のものとがあるので、それぞれ実装して見た目を比べてみます。

まずは6点キャラクタを使ったビットマップ描画クラス。

# -*- mode:python;coding:utf-8 -*-
# bitmap6.py

import curses

class BitMapDisplay(object):
    u'''点字(6点)キャラクタを使ったビットマップ描画
    '''
    P = (
        '⠀⠁⠈⠉⠂⠃⠊⠋' +
        '⠐⠑⠘⠙⠒⠓⠚⠛' +
        '⠄⠅⠌⠍⠆⠇⠎⠏' +
        '⠔⠕⠜⠝⠖⠗⠞⠟' +
        '⠠⠡⠨⠩⠢⠣⠪⠫' +
        '⠰⠱⠸⠹⠲⠳⠺⠻' +
        '⠤⠥⠬⠭⠦⠧⠮⠯' +
        '⠴⠵⠼⠽⠶⠷⠾⠿')
    def __init__(self, scr :'curses._CursesWindow') -> None:
        h, w = scr.getmaxyx()
        self.height, self.width = 3 * (h - 1), 2 * (w - 1)
        self._scr = scr
        self._bmap = [[False for x in range(self.width)]
                      for y in range(self.height)]
        self._lines = ['' for y in range(self.height)]
        scr.clear()
    def clear(self) -> None:
        u'''ビットマップをクリアする。
        '''
        self._bmap = [[False for x in range(self.width)]
                      for y in range(self.height)]
    def set(self, y :int, x :int, b :bool) -> None:
        u'''ビットマップ上に値を設定する。
        y, x -- 画面上の位置(ドット単位)
        '''
        self._bmap[y % self.height][x % self.width] = b
    def get(self, y :int, x :int) -> bool:
        u'''ビットマップ上から値を取得する。
        y, x -- 画面上の位置(ドット単位)
        '''
        return self._bmap[y % self.height][x % self.width]
    def get_character(self, cy :int, cx :int) -> str:
        u'''画面上に表示するキャラクタを取得する。
        cy, cx -- 画面上の位置(キャラクタ単位)。
        '''
        i = sum([(1 << (2 * dy + dx) if self._bmap[3 * cy + dy][2 * cx + dx] else 0)
                 for dy in range(3) for dx in range(2)])
        return BitMapDisplay.P[i]
    def draw(self) -> None:
        u'''画面に描画する。
        '''
        for cy in range(0, self.height // 3):
            # 行の表示内容を取得(末尾の空白部分は削除)
            line = ''.join([self.get_character(cy, cx)
                            for cx in range(self.width // 2)]).rstrip('')
            if self._lines[cy] == line:
                continue  # 書き換え不要
            self._scr.addstr(cy, 0, line)
            self._scr.clrtoeol()  # 行末まで削除
            self._lines[cy] = line

一文字ずつ表示するのは遅そうな気がしたので、行単位で前回と比較して変化があったら描画、ということをしています(実際に測定してみると、文字単位で差分チェックして文字単位で addstr するより行単位で差分チェックして一行まとめて addstr する方が微妙に速い)。

あと、通常の点字の点の並びの概念に合わせると 3 * x + y で順序付けた方がよかったかもしれません。今回は文字コードを使ってキャラクタを生成することはせず、予め文字を並べたテーブル BitMapDisplay.P を準備したので 2 * y + x にしてしまいました。

8点の場合もほとんど同じです。

# -*- mode:python;coding:utf-8 -*-
# bitmap8.py

import curses

class BitMapDisplay(object):
    u'''漢点字フォント(8ドット)を使ったビットマップ表示。
    '''
    P = ('⠀⠁⠈⠉⠂⠃⠊⠋⠐⠑⠘⠙⠒⠓⠚⠛' +
         '⠄⠅⠌⠍⠆⠇⠎⠏⠔⠕⠜⠝⠖⠗⠞⠟' +
         '⠠⠡⠨⠩⠢⠣⠪⠫⠰⠱⠸⠹⠲⠳⠺⠻' +
         '⠤⠥⠬⠭⠦⠧⠮⠯⠴⠵⠼⠽⠶⠷⠾⠿' +
         '⡀⡁⡈⡉⡂⡃⡊⡋⡐⡑⡘⡙⡒⡓⡚⡛' +
         '⡄⡅⡌⡍⡆⡇⡎⡏⡔⡕⡜⡝⡖⡗⡞⡟' +
         '⡠⡡⡨⡩⡢⡣⡪⡫⡰⡱⡸⡹⡲⡳⡺⡻' +
         '⡤⡥⡬⡭⡦⡧⡮⡯⡴⡵⡼⡽⡶⡷⡾⡿' +
         '⢀⢁⢈⢉⢂⢃⢊⢋⢐⢑⢘⢙⢒⢓⢚⢛' +
         '⢄⢅⢌⢍⢆⢇⢎⢏⢔⢕⢜⢝⢖⢗⢞⢟' +
         '⢠⢡⢨⢩⢢⢣⢪⢫⢰⢱⢸⢹⢲⢳⢺⢻' +
         '⢤⢥⢬⢭⢦⢧⢮⢯⢴⢵⢼⢽⢶⢷⢾⢿' +
         '⣀⣁⣈⣉⣂⣃⣊⣋⣐⣑⣘⣙⣒⣓⣚⣛' +
         '⣄⣅⣌⣍⣆⣇⣎⣏⣔⣕⣜⣝⣖⣗⣞⣟' +
         '⣠⣡⣨⣩⣢⣣⣪⣫⣰⣱⣸⣹⣲⣳⣺⣻' +
         '⣤⣥⣬⣭⣦⣧⣮⣯⣴⣵⣼⣽⣶⣷⣾⣿')
    def __init__(self, scr :'curses._CursesWindow') -> None:
        h, w = scr.getmaxyx()
        self.height, self.width = 4 * (h - 1), 2 * (w - 1)
        self._scr = scr
        self._bmap = [[False for x in range(self.width)]
                      for y in range(self.height)]
        self._lines = ['' for y in range(self.height)]
        scr.clear()
    def clear(self) -> None:
        u'''ビットマップをクリアする。
        '''
        self._bmap = [[False for x in range(self.width)]
                      for y in range(self.height)]
    def set(self, y :int, x :int, b :bool) -> None:
        u'''ビットマップ上に値を設定する。
        y, x -- 画面上の位置(ドット単位)
        '''
        self._bmap[y % self.height][x % self.width] = b
    def get(self, y :int, x :int) -> bool:
        u'''ビットマップ上から値を取得する。
        y, x -- 画面上の位置(ドット単位)
        '''
        return self._bmap[y % self.height][x % self.width]
    def get_character(self, cy :int, cx :int) -> str:
        u'''画面上に表示するキャラクタを取得する。
        cy, cx -- 画面上の位置(キャラクタ単位)。
        '''
        i = sum([(1 << (2 * dy + dx) if self._bmap[4 * cy + dy][2 * cx + dx] else 0)
                 for dy in range(4) for dx in range(2)])
        return BitMapDisplay.P[i]
    def draw(self) -> None:
        u'''画面に描画する。
        '''
        for cy in range(0, self.height // 4):
            # 行の表示内容を取得(末尾の空白部分は削除)
            line = ''.join([self.get_character(cy, cx)
                            for cx in range(self.width // 2)]).rstrip('')
            if self._lines[cy] == line:
                continue  # 書き換え不要
            self._scr.addstr(cy, 0, line)
            self._scr.clrtoeol()  # 行末まで削除
            self._lines[cy] = line

まずはテストプログラム

ただ直線を引くだけだと面白くないので、螺旋を描いてみました。
螺旋だけだと面白くないので、螺旋がぐるぐる回って吸い込まれる感じを出してみました。

import curses
import math
import time
from bitmap8 import BitMapDisplay # 8点で描画する場合
# from bitmap6 import BitMapDisplay # 6点で描画する場合

def main() -> None:
    stdscr = curses.initscr()
    try:
        curses.curs_set(0)
        curses.noecho()
        curses.cbreak()
        stdscr.nodelay(True)
        bmd = BitMapDisplay(stdscr)
        scale = min([bmd.height // 2, bmd.width // 2])
        step = 10
        offset = 0.0
        running = True
        start_time = datetime.datetime.now()
        count = 1
        while True:
            if running:
                offset += 0.1
                bmd.clear()
                for t in range(scale * step):
                    t1 = t / step
                    r = t1
                    a = 2 * math.pi * (t1 / scale) * 3
                    (y, x) = (r * math.sin(a + offset),
                              r * math.cos(a + offset))
                    bmd.set(round(bmd.height / 2 + y),
                            round(bmd.width / 2 + x),
                            True)
                bmd.draw()
                current_time = datetime.datetime.now()
                draw_per_sec = count / (current_time - start_time).total_seconds()
                count += 1
                stdscr.addstr(
                    stdscr.getmaxyx()[0] - 1, 1,
                    'width = {}, height = {}, draw/sec = {}'.format(
                        stdscr.getmaxyx()[1],
                        stdscr.getmaxyx()[0],
                        draw_per_sec))
                stdscr.refresh()
                #time.sleep(0.1)  # 回転速度
            else:
                time.sleep(1)
                # reset
                start_time = datetime.datetime.now()
                count = 1
            if stdscr.getch() == ord(' '):
                running = not running
    finally:
        curses.endwin()
if __name__ == '__main__':
    main()

実行すると、こんな感じの表示がぐるぐる回ります。

6点での表示例:


⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠠⠠⠄⠄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠒⠈⠁⠁⠀⠀⠀⠀⠉⠈⠂⠢
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠒⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠂⠂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠂⠄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠎⠀⠀⠀⠀⠀⠀⠀⠠⠤⠤⠒⠤⠤⠄⠀⠀⠀⠀⠀⠀⠀⠈⠂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠌⠀⠀⠀⠀⠀⠀⠔⠊⠉⠀⠀⠀⠀⠀⠈⠑⠢⠄⠀⠀⠀⠀⠀⠈⠂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠀⠀⠀⠀⠀⠠⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⠀⠀⠀⠀⠀⠈⠂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⠀⠀⠀⠀⠀⠘
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠀⠀⠀⠀⠀⠜⠀⠀⠀⠀⠀⠤⠒⠒⠒⠦⠄⠀⠀⠀⠀⠀⠣⠀⠀⠀⠀⠀⠅
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠀⠀⠀⠀⠀⠇⠀⠀⠀⠀⠰⠁⠀⠀⠀⠀⠱⠄⠀⠀⠀⠀⠸⠀⠀⠀⠀⠀⠨
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⠀⠀⠀⠀⠀⠇⠀⠀⠀⠀⠸⠄⠰⠀⠀⠀⠀⠧⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠐
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠀⠀⠀⠀⠀⠣⠀⠀⠀⠀⠀⠉⠉⠀⠀⠀⠀⠜⠀⠀⠀⠀⠀⠇⠀⠀⠀⠀⠘
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠄⠀⠀⠀⠀⠈⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠇⠀⠀⠀⠀⠰⠁⠀⠀⠀⠀⠨
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠢⠀⠀⠀⠀⠀⠈⠢⠀⠀⠀⠀⠀⠀⠀⠠⠊⠀⠀⠀⠀⠀⠰⠀⠀⠀⠀⠀⠐
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠉⠒⠤⠤⠤⠔⠚⠁⠀⠀⠀⠀⠀⠠⠃⠀⠀⠀⠀⠀⠃
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠃⠀⠀⠀⠀⠀⠌
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠔⠁⠀⠀⠀⠀⠀⠨
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠄⠄⠀⠀⠀⠀⠀⠀⠀⠀⠠⠔⠁⠀⠀⠀⠀⠀⠀⠠⠂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠒⠒⠐⠒⠐⠊⠈⠁⠀⠀⠀⠀⠀⠀⠀⠔
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠈
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁

 width = 56, height = 24, draw/sec = 650.9813575689599

8点での表示:


⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⢀⡀⡀⣀⢀⢀⡀⡀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠠⠄⠒⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⠐⠂⢄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠐⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠠⡀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠂⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⡀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠤⠤⠤⠤⠤⢄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠀⠀⠀⠀⠀⠀⠀⠀⢠⠔⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠑⡀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡔⠀⠀⠀⠀⠀⠀⠀⢠⠜⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⣄⠀⠀⠀⠀⠀⠀⠀⠈⢄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠀⠀⠀⠀⠀⠀⠀⢰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠐⡀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡃⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⡠⠔⠒⠒⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠡
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠁⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀⠀⠀⠀⠀⠙⣆⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠈⡂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⡄⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⢇⠀⢠⠀⠀⠀⠀⠀⠸⡀⠀⠀⠀⠀⠀⠀⢨⠀⠀⠀⠀⠀⠀⠀⡂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠆⠀⠀⠀⠀⠀⠀⠘⡄⠀⠀⠀⠀⠀⠀⠀⠉⠉⠀⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠅
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⠀⠀⠀⠀⠀⠀⠀⠘⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠀⠀⠀⠀⠀⠀⠀⡔⠀⠀⠀⠀⠀⠀⠀⡅
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠜⠁⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⢐
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⠤⠤⣄⠤⠤⠖⠊⠁⠀⠀⠀⠀⠀⠀⠀⢀⠅⠀⠀⠀⠀⠀⠀⠀⠆
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⠀⠀⠀⡨
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⡐
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠠⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⢀⠌
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠒⠐⠢⠠⠤⠠⠔⠂⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠠⠂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠠⠂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠠⠂⠁

 width = 56, height = 24, draw/sec = 501.90098717464485

スペースキーを押すと描画を停止するように作ったので、適当なタイミングで止めて描画内容をテキストとしてコピーできるのが地味に便利です。

当方の環境だと、コンソールを画面いっぱいに広げても秒間 70 フレームを越えるので、描画速度が問題になることはまずないと思います。

ライフゲーム

ビットマップの描画に問題はなさそうなので2値セル・オートマトンに使ってみることにします。
というわけでライフゲームです。

# -*- mode:python;coding:utf-8 -*-
u'''
点字フォントを使って表示するライフゲーム。
'''
import collections
import curses
import time
from bitmap8 import BitMapDisplay

class Life(object):
    u'''ライフゲーム
    '''
    def __init__(self, bmd :BitMapDisplay) -> None:
        u'''初期状態では「生」状態セルはなし。
        '''
        self._bmd = bmd
        self._cells :set[tuple[int, int]] = set()
    def set(self, y :int, x :int) -> None:
        u'''「生」状態セルを置く。
        '''
        self._cells.add((y, x))
    def next_step(self) -> None:
        u'''次ステップでのセル状態を決定する。
        '''
        self._cells = {
            point
            for point, n in collections.Counter(
                    [self._clipping((y + dy, x + dx))
                     for (y, x) in self._cells
                     for dy in range(-1, 2)
                     for dx in range(-1, 2)
                     if dy != 0 or dx != 0]).items()
            if n == 3 or (n == 2 and point in self._cells)}
        self._bmd.clear()
        for (y, x) in self._cells:
            self._bmd.set(y, x, True)
    def _clipping(self, point : tuple[int, int]) -> tuple[int, int]:
        u'''境界条件処理(周期的境界条件を用いる)。'''
        (y, x) = point
        return (y % self._bmd.height, x % self._bmd.width)
def main() -> None:
    stdscr = curses.initscr()
    try:
        curses.curs_set(0)
        curses.noecho()
        curses.cbreak()
        stdscr.nodelay(True)
        bmd = BitMapDisplay(stdscr)
        life = Life(bmd)
        center_y, center_x = bmd.height // 2, bmd.width // 2
        life.set(center_y, center_x)
        life.set(center_y, center_x + 1)
        life.set(center_y - 2, center_x + 1)
        life.set(center_y - 1, center_x + 3)
        life.set(center_y, center_x + 4)
        life.set(center_y, center_x + 5)
        life.set(center_y, center_x + 6)
        running = True
        while True:
            bmd.draw()
            stdscr.refresh()
            if running:
                life.next_step()
                #time.sleep(0.01)
            else:
                time.sleep(1)
            if stdscr.getch() == ord(' '):
                running = not running
    finally:
        curses.endwin()
if __name__ == '__main__':
    main()

実行すると、こんな感じの表示が動きます。

6点での表示:

⠀⠀⠀⠀⠀⠀⠀⠠⠳
⠀⠀⠀⠀⠀⠀⠤
⠀⠤⠚⠦⠀⠘⠌⠆
⠆⠏⠀⠨⠀⠀⠁⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠴⠑⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠
⠑⠶⠋⠩⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠐⠀⠱
⠀⠀⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀⠀⠀⠜⠀⠀⠀⠀⠀⠤
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠂⠈⠀⠀⠀⠀⠀⠀⠉

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠄⠀⠀⠀⠀⠀⠠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠠
⠀⠀⠀⠰⠆⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠪⠕⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠩⠜
⠀⠀⠀⠀⠀⠀⠏⠂⠀⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠨⠕
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠢⠀⠀⠀⠒⠂⠀⠀⠀⠀⠀⠀⠀⠤
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠬⠠⠑⠒⠄⠀⠀⠠⠒⠄⠀⠈⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉
⠀⠀⠀⠀⠀⠀⠀⠠⠄⠉⠊⠀⠒⠁⠀⠀⠈⠒⠁
⠀⠀⠀⠀⠀⠀⠀⠈⠁

⠀⠀⠀⠀⠀⠀⠀⠘⠃
⠀⠀⠀⠀⠀⠔⠢⠀⠀⠠⠀⠄
⠀⠀⠀⠀⠀⠈⠁⠀⠀⠼⠭⠸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠚⠀⠀⠀⠀⠀⠀⠀⠬⠆
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠰
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠧
 generation: 333

8点での表示:

⠀⠀⠀⠀⠀⠀⠀⠀⡀
⠀⠀⠀⠀⠀⠀⠀⠐⠙
⠀⣀⠴⣄⠀⠰⡙⡄
⡆⠏⣀⣨⠀⠀⠁⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠴⠑⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠
⠈⠛⠁⡰⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠈⠈⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⠄⠐⠁⠀⠀⠀⠀⠀⠛

⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠰⠆⠀⠀⡠⢄⠀⠰⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠴⢰
⠀⠀⠀⠈⠁⠀⡖⠄⠀⠶⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢐⡢⠀⠈⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠠⡀⠀⠀⠀⠀⠀⠀⠀⠀⢠⢢⠀⠀⠀⠒⠂⠀⠀⠀⠀⠀⠀⠀⣤
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠶⡰⠈⣉⠆⠀⠀⠰⣉⠆⠀⠀⠁
⠀⠀⠀⠀⠀⠀⠀⠘⠃
⠀⠀⠀⠀⠀⠀⠀⢀⡀
⠀⠀⠀⠀⠀⢀⡀⠈⠁
⠀⠀⠀⠀⠀⠑⠊⠀⠀⣸⣒⢱
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠚⠀⠀⠀⠀⠀⠀⠀⠬⠆
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡜
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⡭⠂


 generation: 333

まとめ

  • curses と点字を組み合わせて、時間的に変化するビットマップの描画を行いました。
  • GUI を持ち出すほどでもないけれどもキャラクタでの表示だとやや解像度が不足している、というときには便利です。例えば、2値セル・オートマトンの描画に使えます。
  • 6点の点字と、8点の点字がありますが、自分の環境では8点の方が6点に比べてドット間隔の均一さの面で勝っていると感じました。
  • 文字間隔が均一なコンソールターミナル上での表示を前提にしているので、描画内容をテキストとして他の環境に持っていくと崩れて見える可能性があります。
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?