勇気ある加藤の猛り

元気はないけど、勇気があります

DCジャックDIP化キットでArduinoの外部電源としてACアダプタを利用する

f:id:katoh4u:20180920183320j:plain
こんにちは勇気ある加藤です。
電子工作をしていて困るのが電源の用意です。
僕は主にArduinoを使っているのですが、Arduinoから供給できる電源は5Vと3.3Vです。
これ以外の電圧が必要だったり消費電力が大きい部品の電源をArduinoからとるとArduinoが壊れてしまいます。


そこで外部電源を用意するわけですが、そのさいに便利なのがACアダプタとDCジャックのセットです。
ACアダプタには様々な電圧、電流のものがあるので、ほとんどの部品はACアダプタを変えることで対応できます。
今回はDCジャックDIP化キットで、DCジャックをブレッドボードで使えるようにします。

キットの用意

秋月電子でこれを買いました。1セット100円程度です。
ブレッドボード用DCジャックDIP化キット: 組立キット 秋月電子通商-電子部品・ネット通販


届いた時はこんな感じ
f:id:katoh4u:20180920183233j:plain


以下の部品が入っています。
・基盤
・DCジャック
・ピンヘッダ×2
f:id:katoh4u:20180920183245j:plain



組み立て

DCジャック部品から出ている三本の足を、基盤の表側から裏側に差します。
f:id:katoh4u:20180920183255j:plain


足を折り曲げます。
f:id:katoh4u:20180920183305j:plain
ちなみに僕は一番長い脚がどうしても曲げられなかったので、写真では一本だけ真っすぐになっています。
結果としてブレッドボードにさすときに、ブレッドボードと足がぶつかって若干傾くようになったので、しっかり曲げることが大切だという学びを得ました。


はんだ付けします。
f:id:katoh4u:20180920183313j:plain


次に、ピンヘッダを足の長い方が下になるように基盤の裏側から表側に差して、表側からはんだづけしたら完成です。
四本のピンのうち、+と書かれている側の2本がプラス、もう2本がマイナスとなっています。
f:id:katoh4u:20180920183320j:plain


ブレッドボードで使う際は、ブレッドボードのみぞをまたぐように差します。
f:id:katoh4u:20180920183328j:plain


このDCジャックが一つあるだけで、ACアダプタを変えていろいろな仕様の電源を簡単に用意できるのでとても便利です。

Bottle.pyでTemplate 'index.html' not found.になった時の対処法

f:id:katoh4u:20180330103921j:plain


こんにちは勇気ある加藤です。

Bottle.pyでテンプレートを使う際にエラーでハマったのでメモです。
ディレクトリの構成はこんな感じです

/
├── views/
│   ├── index.html
└── index.py


そして、以下のコードを実行したところTemplate 'index.html' not found.というエラーが出ました。

#!/usr/local/bin/python3

from bottle import *
TEMPLATE_PATH.append('/views')

@route('/')
def index():
    return template("index.html")

if __name__=='__main__':
    run(host='localhost', port=8080, debug=True)



実行ディレクトリがルートではなかったことが原因のようです

このコードはVSCodeの内蔵ターミナルで実行していたのですがVSCodeのデフォルトの設定では、実行ディレクトリがC:\Users\hogeになるため、パスの指定がうまくいかなかったようです。別途ターミナルを立ち上げてルートまで移動し、index.pyを実行することで解決しました。単純なミスですがハマってしまったので反省です。さよなら

Pythonで数字を反時計回りの螺旋状に表示するアルゴリズム

こんにちは勇気ある加藤です。

指定された巻き数の、渦巻きを表示するプログラムをPythonで書きました。
ちなみに数字をらせん状に並べると、素数の分布に規則性があるとかないとかというのが、下図の「ウラムの螺旋」になりますが、ちょっと何言ってんのか分かんないので、このブログでは数字を並べるまでにとどめておきます。


https://upload.wikimedia.org/wikipedia/commons/thumb/1/1d/Ulam_spiral_howto_all_numbers.svg/266px-Ulam_spiral_howto_all_numbers.svg.png
https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Ulam_spiral_howto_primes_only.svg/266px-Ulam_spiral_howto_primes_only.svg.png

wikipedia: ウラムの螺旋



使用するモジュールについて

itertools

itertoolsモジュールはイテレータに関するさまざまなコンテナを提供してくれる、めちゃくそ便利なモジュールです。すべてのPythonistaにとっての頼れる兄貴的存在ですね。今回は循環リストitertools.cycleを使うためにインポートします。

math

mathモジュールは、math.log10で数値の桁数をもとめるためにインポートしています。



実装

import math
import itertools



def print_spiral(spiral):
    """
    螺旋の二次元リストを整形して表示する

    args: [[4, 3, 2], [5, 0, 1], [6, 7, 8]]
    output:
        4 3 2
        5 0 1
        6 7 8
    """

    # 最大値の桁数を求める
    end = spiral[-1][-1]
    digit = int(math.log10(end) + 1)

    # 桁数分だけ右詰めする書式をつくる
    base = "{: >" + str(digit) + "} "

    for i in spiral:
        for j in i:
            print(base.format(j), end="")
        print("\n", end="")



if __name__ == "__main__":
    LOOP = 4
    WIDTH = (2*LOOP) + 1

    E = (1, 0)
    N = (0, -1)
    W = (-1, 0)
    S = (0, 1)
    DIRECTION = itertools.cycle((E, N, W, S))

    x = LOOP
    y = LOOP
    step = 1    # 進んだ距離
    corner = 1  # まがり角の位置

    # 二次元リストを初期化
    spiral = []
    for i in range(WIDTH):
        spiral.append([0 for j in range(WIDTH)])

    for i in range(WIDTH * WIDTH):
        # まがり角に到達したら方向転換
        if step >= corner:
            step = 1
            direction = next(DIRECTION)
            dx, dy = direction

            # X方向に進むとき、まがり角が遠くなる
            if direction == E or direction == W:
                corner += 1
                
        spiral[y][x] = i
        step += 1
        x += dx
        y += dy

    # 螺旋を表示する
    print_spiral(spiral)


LOOPの値を変えると巻き数が変わります。stepcornerという変数が、このプログラムのみそで、以下の画像のように、ある角から次の角までの距離は、東方向と西方向へ方向転換する際に増加しています。そのため現在の移動量をstep、次の曲がり角までの距離をcornerとして比較しています。
f:id:katoh4u:20180324194815p:plain



まとめ

私は過去にプログラムクイズにハマっていた時期がありまして、今回の問題も過去のソースをあさっていたら出てきたものです。最初に書いた時から3~4年たっているので、さすがに前よりはうまく書けるだろうと思い書き直してみたのですが、書き直したコードも全然よくできたアルゴリズムではないですね。まあ要件は満たしているし、私は自分に優しいタイプなのでこれで良しとしておきます。さよなら

Python初心者は練習問題としてリバーシ(オセロ)を実装するべき

こんにちは勇気ある加藤です
私がはじめてプログラミングを学んだときは、主に入門サイトで演習問題を解くなどして文法を覚えたのですが、プログラミングの入門ってだいたい、Hello, Worldして、繰り返しで数を列挙して、乱数を使った数あてゲームを作ったりしてーといった感じですよね。
そうして解説などを読みながら「うんうんうん、なるほどね、繰り返す処理とかが得意なんだね、へー……」




で?




de???




ってなりますよね。



そこでリバーシですよ

そこで「脱入門サイト」として、初心者はリバーシの実装をしてみるのがよいでしょう
理由としては以下の通りです
ボードゲームとしてシンプルで実装しやすい
・他のボードゲームへ応用しやすい
・繰り返し処理、二次元配列、(ちょっとした)アルゴリズムなどを実践的に学べる
・出来上がったもので遊べる(たのしい)

ボードクラスの実装

リバーシとは?
リバーシは8×8のマスが描かれた盆上に、白色の面と黒色の面を持つ平たい石を置くなどして、10分程度を浪費する遊戯です。「石」というのは正式名称らしいですが、本当に石を使っている商品を見たことがないと思いませんか。

さて、まずは使いそうな定数を定義します。
白手と黒手のプレイヤーを表現するためにWHITEと>BLACKを定義し、ボードの大きさをBOARD_SIZEとしました。中身はただの数字ですが、名前を付けることで用途が明示的になります。

WHITE = 0
BLACK = 1
BOARD_SIZE = 8

次にボードをクラスとして定義します。
マスは2次元配列(リスト)で表現することにして、初期値はすべてNoneとします。

class ReversiBoard(object):
    def __init__(self):
        # 2次元リストを生成する
        # 各要素の初期値はNone
        self.cells = []
        for i in range(BOARD_SIZE):
            self.cells.append([None for i in range(BOARD_SIZE)])

        # 4つの石を初期配置する
        self.cells[3][3] = WHITE
        self.cells[3][4] = BLACK
        self.cells[4][3] = BLACK
        self.cells[4][4] = WHITE


インスタンスを生成して、二次元リストが作られていることを確認してみます

>>> board = ReversiBoard()
>>> board.cells
[[None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, 0, 1, None, None, None],
 [None, None, None, 1, 0, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None]]

石を置くメソッド

さて、ボードができたので次に石を置くメソッドを作ります。
メソッド名はput_diskとします。x座標、y座標、石を置くプレイヤーの3つを引数に取ることにします。

class ReversiBoard(object):
    def put_disk(self, x, y, player):
        """指定した座標に指定したプレイヤーの石を置く
        Args:
            x: 置く石のX座標
            y: 置く石のY座標
            player: 石を置こうとしているプレイヤー(WHITEまたはBLACK)

        Returns:
            True: 関数の成功を意味する. 指定した座標と
                それによって獲得できる石がすべてplayerの色になった場合に返す
            False: 関数が以下のいずれかのケースによって失敗した場合に返す
                ・指定した座標に既に別の石がある
                ・指定した座標に石を置いても相手側の石を獲得できない
        """

        # 既にほかの石があれば置くことができない
        if self.cells[y][x] is not None:
            return False

        # 獲得できる石がない場合も置くことができない
        flippable = self.list_flippable_disks(x, y, player)
        if flippable == []:
            return False

        # 実際に石を置く処理
        self.cells[y][x] = player
        for x,y in flippable:
            self.cells[y][x] = player

        return True

多くの場合、座標を指定するときは(x, y)という形式にすると思うのですが、
self.cells[y][x]のようにY座標から先に指定しています。
これはself.cellsが[ボードの1行目のリスト、2行目の… N行目]というように
ボードの行を要素に持つリストであるためです。

このメソッドは成功したらTrue、失敗したらFalseを返すことにします。
置こうとしている位置に既にほかの石があれば、置くことができないのでメソッドは失敗です。Falseを返します。

self.list_flippable_disks(x, y, player)は次に実装するメソッドです。
このメソッドは「その石を置くことによってひっくりかえせる石のリスト」を返すことにしましょう。
そのリストが空であれば「ひっくりかえすことができる相手の石の数が0」ということなので、指定した場所に石を置くことはできません。Falseを返します。

実際に石を置く処理は、self.cellsの指定の位置に、プレイヤーの色を代入することにしました。
自分の石を置いたことによってひっくり返る、相手の石もここで更新します。


ひっくりかえせる石の数を列挙するメソッド

指定した座標にプレイヤーの石を置いた時、ひっくりかえせる石の座標をリストにして返します

class ReversiBoard(object):
    def list_flippable_disks(self, x, y, player):
        """指定した座標に指定したプレイヤーの石を置いた時、ひっくりかえせる全ての石の座標(タプル)をリストにして返す
        Args:
            x: x座標
            y: y座標
            player: 石を置こうとしているプレイヤー

        Returns:
            ひっくりかえすことができる全ての石の座標(タプル)のリスト
            または空リスト
        """

        PREV = -1
        NEXT = 1
        DIRECTION = [PREV, 0, NEXT]
        flippable = []

        for dx in DIRECTION:
            for dy in DIRECTION:
                if dx == 0 and dy == 0:
                    continue

                tmp = []
                depth = 0
                while(True):
                    depth += 1

                    # 方向 × 深さ(距離)を要求座標に加算し直線的な探査をする
                    rx = x + (dx * depth)
                    ry = y + (dy * depth)

                    # 調べる座標(rx, ry)がボードの範囲内ならば
                    if 0 <= rx < BOARD_SIZE and 0 <= ry < BOARD_SIZE:
                        request = self.cells[ry][rx]

                        # Noneを獲得することはできない
                        if request is None:
                            break

                        if request == player:  # 自分の石が見つかったとき
                            if tmp != []:      # 探査した範囲内に獲得可能な石があれば
                                flippable.extend(tmp) # flippableに追加

                        # 相手の石が見つかったとき
                        else:
                            # 獲得可能な石として一時保存
                            tmp.append((rx, ry))
                    else:
                        break
        return flippable


DIRECTIONの中身は実際には[-1, 0, 1]というなんてことないリストです。
この値はそれぞれ負の方向、水平垂直、正の方向を表します。これらの組み合わせで図のように方向を決めます。
方向に距離をかけることで8方向への探査を実現しています。
f:id:katoh4u:20180322115258p:plain




if 0 <= rx < BOARD_SIZE and 0 <= ry < BOARD_SIZEの部分ですが、
if文の中は長くelseの中は一行となっています。そのため、条件を変えて以下のようにすると、インデントが深くなりすぎなくてよいのですが

if 0 > rx or rx >= BOARD_SIZE or 0 > ry or ry >= BOARD_SIZE:
    break


個人的には、元の条件式の書き方は図のように、数直線で考えられるので直感的で好きです。
f:id:katoh4u:20180322121126p:plain

ゲームにする

さて、基本的なシステムはほとんど出来ましたが
コマンドラインで操作できるゲームにするためには、
①ボードを文字で表示する
②プレイヤーが置くことができる石のリストを表示する
という機能があるとよいでしょう

    def show_board(self):
        """ボードを表示する"""
        print("--" * 20)
        for i in self.cells:
            for cell in i:
                if cell == WHITE:
                    print("W", end=" ")
                elif cell == BLACK:
                    print("B", end=" ")
                else:
                    print("*", end=" ")
            print("\n", end="")

すべてのマスを調べて、文字を表示するだけなのでむずかしくないですね



プレイヤーが置くことができるマスのリストを返すメソッドもすべてのマスに対してself.list_flippable_disksを呼ぶだけです。

    def list_possible_cells(self, player):
        """指定したプレイやーの石を置くことができる、すべてのマスの座標をリストにして返す
        Args:
            player: 石を置こうとしているプレイヤー

        Returns:
            石を置くことができるマスの座標のリスト
            または空リスト
        """

        possible = []
        for x in range(BOARD_SIZE):
            for y in range(BOARD_SIZE):
                if self.cells[y][x] is not None:
                    continue
                if self.list_flippable_disks(x, y, player) == []:
                    continue
                else:
                    possible.append((x, y))
        return possible

動くか試してみます

if __name__ == "__main__":
    board = ReversiBoard()
    board.show_board()
    board.put_disk(3, 2, BLACK)
    board.show_board()


以下のように表示されました。ちゃんと動いてますね。

----------------------------------------
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * W B * * *
* * * B W * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
----------------------------------------
* * * * * * * *
* * * * * * * *
* * * B * * * *
* * * B B * * *
* * * B W * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *

ゲームクラスをつくる

最後に、勝ち負けの判定や、プレイヤーの手番の管理などをGameというクラスにまとめましょう

ReversiBoardを継承して、いくつかのメソッドを追加します。リバーシのルールでは引き分けを認める場合と、あらかじめどちらかのプレイヤーに「石の数が同じだった場合に勝利する権利」を認める場合があります。今回は引き分けを認める実装にしました。その方が簡単なので。

class Game(ReversiBoard):
    DRAW = -1

    def __init__(self, turn=0, start_player=BLACK):
        super().__init__()
        self.player = start_player
        self.turn = turn
        self.winner = None
        self.was_passed = False

    def is_finished(self):
        return self.winner is not None

    def list_possible_cells(self):
        return super().list_possible_cells(self.player)

    def get_color(self, player):
        if player == WHITE:
            return "WHITE"
        if player == BLACK:
            return "BLACK"
        else:
            return "DRAW"

    def get_current_player(self):
        return self.player

    def get_next_player(self):
        return WHITE if self.player == BLACK else BLACK

    def shift_player(self):
        self.player = self.get_next_player()

    def put_disk(self, x, y):
        if super().put_disk(x, y, self.player):
            self.was_passed = False
            self.player = self.get_next_player()
            self.turn += 1
        else:
            return False

    def pass_moving(self):
        if self.was_passed:
            return self.finish_game()

        self.was_passed = True
        self.shift_player()

    def show_score(self):
        """それぞれのプレイヤーの石の数を表示する"""
        print("{}: {}".format("BLACK", self.disks[BLACK]))
        print("{}: {}".format("WHITE", self.disks[WHITE]))

    def finish_game(self):
        self.disks = self.get_disk_map()
        white = self.disks[WHITE]
        black =self.disks[BLACK]

        if white < black:
            self.winner = BLACK
        elif black < white:
            self.winner = WHITE
        else:
            self.winner = self.on_draw()

        return self.winner

    def on_draw(self):
        """ゲーム終了時に両社の石の数が同数だった時の処理
        デフォルトでは引き分けを認める
        """
        return self.DRAW


メインループはこんな感じにすれば一人二役の悲しいオセロゲームが出来上がりです。

if __name__ == "__main__":
    game = Game()
    while(True):
        possible = game.list_possible_cells()
        player_name = game.get_color(game.get_current_player())

        if game.is_finished():
            game.show_board()
            game.show_score()
            print("Winner: {}".format(game.get_color(game.winner)))
            break

        if possible == []:
            print("player {} can not puts.".format(player_name))
            game.pass_moving()
            continue

        game.show_board()
        print("player: " + player_name)
        print("put to: " + str(possible))
        index = int(input("choose: "))

        game.put_disk(*possible[index])


それでは皆さんよい一人オセロライフを。さよなら

はてな記法で記事中にソースコードを埋め込んでみる

こんにちは勇気ある加藤です
プログラミングに関するブログをはじめたので
ソースコードをきれいにハイライトして記事に埋め込みたい!」と念じたところ、はてな記法を使って半角の「>||」「||<」で囲えばよいという神の啓示を受けたのでご報告します。

class RevelationOfGodException(Exception):
    pass

try:
    raise RevelationOfGodException("""hatena kihou wo tsukau no desu""")
except Exception as e:
    print(e)

ちなみにインターネット上にこの記事とまったく同じことを書かれている方が100億人(概算)いたのでこの記事自体がすでに存在する情報の劣化コピー劣化コピー劣化コピーの……(無限の繰り返し)であり、トラフィックの無駄遣いであることはまず間違いないわけですが、しかし同時に他の誰かが書いていても私自身は書いていないのであり、このブログは他の誰かがやっている事もビシバシ書いていこうと考えています。さよなら