18
18

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

絵によるプログラミング

Last updated at Posted at 2016-03-11

絵でプログラムを書いてみよう

次のような、画像(picntu.png)を書きます。

image

この画像を解析して実行することができます。上側は、部品置き場です。下側を実行します。
左上から右に進み、下に移っていきます。Pythonと同じく、インデントでブロックを表します。
タートルグラフィックスのように、最初に座標(x=0, y=0)にいて、北を向いています。コマンドで移動したりできます。

サンプルの実行

bash
docker run -it --rm tsutomu7/picntu ./picntu picntu.png
>>>
0 0
0 2
2 2
2 0

部品の説明

  • go: 1歩進みます。(最初に(0, 0)にいます。)
  • back: 1歩戻ります。
  • left: 左を向きます。(最初に北を向いています。)
  • right: 右を向きます。
  • def 数字: 数字のサブルーチンを定義します。定義の範囲は、インデントで決まります。
  • run 数字: 数字のサブルーチンを実行します。
  • lop 数字: 数字の分、繰り返します。繰り返しの範囲は、インデントで決まります。
  • pri 数字: 数字を出力します。
  • if= 数字: 数字がメモリの値と等しいか判定します。範囲は、インデントで決まります。
  • if< 数字: 数字がメモリの値を超えるか判定します。
  • if> 数字: 数字がメモリの値未満か判定します。
  • set 数字: メモリに数字を設定します。
  • add 数字: メモリに数字を足します。
  • sub 数字: メモリから数字を引きます。
  • 数字を空欄にすると、メモリの値を用います。メモリには、1つの数字しか持てません。
  • 数字は、0から9までで、10は0に戻ります。
  • 数字のかわりに、下記のリテラルも使えます。

リテラル

  • 数字: 数字は、0から9までで、10は0に戻ります。
  • i,j: lopのカウンタです。最初のループがiで、2番目のループがjになります。
  • x,y: 座標を表します。
  • r: 向きを表します。北=0、東=1.南=2、西=3。

サンプルの説明

サンプル
def 0 | pri x | pri y
lop 4 | run 0
      | lop 2 | go
      | right

説明

  • 以下をサブルーチン0とする
    • x と y を出力
  • 以下を4回繰り返す
    • サブルーチン0をよぶ
    • 2回、前に進む(2歩進む)
    • 右を向く

任意の画像の実行

/tmpに画像ファイルを入れれば、次のように実行できます。絵のサイズは変更できますが、部品の位置は変えないでください。

bash
docker run -it --rm -v /tmp:/tmp tsutomu7/picntu ./picntu /tmp/画像ファイル名

第2引数を何かしら指定すると、画像から作成されたpythonを見ることができます。

bash
docker run -it --rm tsutomu7/picntu ./picntu picntu.png 0
>>>
i, j, x, y, r, c = 0, 0, 0, 0, 0, 0
dx, dy = [0, 1, 0, 9], [1, 0, 9, 0]
def f0():
    print("\n%d" % x, end="");print(" %d" % y, end="");
for i in range(4):
    f0();
    for j in range(2):
        globals()["x"], globals()["y"] = (x + dx[r]) % 10, (y + dy[r]) % 10;
    globals()["r"] = (r + 1) % 4;

0 0
0 2
2 2
2 0

ブラウザで実行

下記を実行して、ファイルを指定して"run"を押してください。

bash
docker run -it -d -p 5000:5000 tsutomu7/picntu
firefox localhost:5000

image

解析プログラム

picntu.py
import cv2, sys, unionfind

if len(sys.argv) <= 1:
    print('%s image_file' % sys.argv[0][:-3])
    exit()
im = cv2.imread(sys.argv[1])
imtop = im[5:13, 4:86] # top of card
r1 = cv2.matchTemplate(im[160:], imtop, cv2.TM_CCOEFF_NORMED)
scom = 'go back lop if_eq set ' \
       'left right else if_lt add ' \
       'def run pri if_gt sub'.split()
slit = '0 1 i 2 3 j 4 5 x 6 7 y 8 9 r'.split()
coms = [] # command list
for y, x in [(i, j) for i, p in enumerate(r1 >= 0.999)
                    for j, q in enumerate(p) if q]:
    imcom = im[y+173:y+197,x+9:x+49]
    r2 = cv2.matchTemplate(im[:150,:460], imcom, cv2.TM_CCOEFF_NORMED)
    _, v, _, (x2, y2) = cv2.minMaxLoc(r2)
    if v < 0.999:
        continue
    i = (x2+33)//92 + (y2+8)//52*5
    arg = 'c'
    if i > 7 or (i+1)%6 > 2:
        r3 = cv2.matchTemplate(im[9:147,465:545], im[y+173:y+193,x+53:x+70],
                               cv2.TM_CCOEFF_NORMED)
        _, v, _, (x3, y3) = cv2.minMaxLoc(r3)
        if v >= 0.999:
            arg = slit[(x3+8)//26 + (y3+7)//25*3]
    coms.append([-1, -1, scom[i], arg, x, y])

n = len(coms)
ux = unionfind.unionfind(n)
uy = unionfind.unionfind(n)
for i in range(n):
    coi = coms[i]
    for j in range(i+1, n):
        coj = coms[j]
        if abs(coi[4] - coj[4]) < 12:
            ux.unite(i, j)
        if abs(coi[5] - coj[5]) < 12:
            uy.unite(i, j)
for i, g in enumerate(sorted(ux.groups(), key=lambda g: coms[g[0]][4])):
    for j in g:
        coms[j][1] = i
for i, g in enumerate(sorted(uy.groups(), key=lambda g: coms[g[0]][5])):
    for j in g:
        coms[j][0] = i

ex = []
ex.append('i, j, x, y, r, c = 0, 0, 0, 0, 0, 0\n')
ex.append('dx, dy = [0, 1, 0, 9], [1, 0, 9, 0]\n')
pr, s, lp = 0, ' ', []
for v, t, com, a, _, _ in sorted(coms):
    if v > pr or s[-1] == ':':
        ex.append('\n' + '    '*t)
    if com == 'go':
        s = 'globals()["x"], globals()["y"] = (x + dx[r]) % 10, (y + dy[r]) % 10;'
    elif com == 'back':
        s = 'globals()["x"], globals()["y"] = (x + dx[(r + 2) % 4]) % 10, (y + dy[(r + 2) % 4]) % 10;'
    elif com == 'left':
        s = 'globals()["r"] = (r + 3) % 4;'
    elif com == 'right':
        s = 'globals()["r"] = (r + 1) % 4;'
    elif com == 'def':
        s = 'def f%s():' % a
    elif com == 'run':
        s = 'f%s();' % a
    elif com == 'lop':
        while lp and lp[-1] >= t:
            lp.pop()
        s = 'for %s in range(%s):' % ('ijklmn'[len(lp)], a)
        lp.append(t)
    elif com == 'else':
        s = 'else:'
    elif com == 'pri':
        s = 'print("%s%%d" %% %s, end="");' % (' ' if v == pr and s[0] == 'p' else '\\n', a)
    elif com == 'if_eq':
        s = 'if c == %s:' % a
    elif com == 'if_lt':
        s = 'if c < %s:' % a
    elif com == 'if_gt':
        s = 'if c > %s:' % a
    elif com == 'set':
        s = 'globals()["c"] = %s;' % a
    elif com == 'add':
        s = 'globals()["c"] += %s;' % a
    elif com == 'sub':
        s = 'globals()["c"] -= %s;' % a
    ex.append(s)
    pr = v
if len(sys.argv) > 2:
    print(''.join(ex))
exec(''.join(ex))
print()

参考:
タートルグラフィックスで亀と戯れる (前編)
3歳児でもオモチャでプログラミングを学習できる「Cubetto」

18
18
1

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
18
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?