2016年07月05日

 被写界深度の計算式を導いてみた

被写界深度というのは、レンズを通して見た映像の、ピントが合っているように見える、奥行き方向の範囲のことである。
昨年、フォトマスター検定という試験を受ける為に勉強した中で、被写界深度を求める式が何故このようになるのかがわからず、気になっていた。
先月、フォトマスター検定について講義する機会があったので、そのネタとして、被写界深度の式を導いてみた。


図1

まず、被写界深度が何故存在するかというと、ピントが合っていない距離の点は点でなく丸になって映る(ボケる)が、その丸(錯乱円)が十分に小さいと、ほとんどボケてるように見えないからである。
その十分に小さい錯乱円の直径=許容ボケをδとすると、フィルム(デジカメだとセンサー)上のδに対応して、光軸上のフィルム/センサーの前後に、その範囲に焦点を結ぶとピントが合って見える範囲=焦点深度が存在する。

図2

この焦点深度の幅を2εとすると、F値(絞り値)=レンズ焦点距離(f)÷レンズ口径(Φ)を使って、f >> εなのでε/δ = F、すなわちε = Fδと近似できる。
この焦点深度に対応する、被写体側の範囲が、被写界深度である。

ここで、「ガウスの結像公式」(以下、単に「結像公式」)というのを用いる。結像公式とは、凸レンズからa離れた位置から発せられる光が、凸レンズを通って反対側のb離れた位置に焦点を結ぶ時、aとbの関係を、レンズ焦点距離fを用いて、
\frac{1}{f}=\frac{1}{a}+\frac{1}{b}, Gaussian form of the lens equation
と表すものである。ちなみに、レンズの焦点距離とは、無限遠から来る光が焦点を結ぶ位置のことである。(上式でa=∞とするとb=fとなる)

この結像公式を用いて、焦点深度と被写界深度の関係を式にする。
図1のように、sをレンズから合焦位置までの距離、snを被写界の近点、sfを被写界の遠点、tをレンズからフィルム/センサーまでの距離とすると、

となる。これらの連立方程式を、tを消去して、snとsfについて解く。
(1)よりt=sf/(s-f)が得られるので、これを(2),(3)に代入すると、
s_n=\frac{(t+\epsilon)f}{(t+\epsilon)-f}=\frac{(\frac{sf}{s-f}+\epsilon)f}{(\frac{sf}{s-f}+\epsilon)-f}=\frac{(sf+\epsilon(s-f))f}{f^2+\epsilon(s-f)}
s_f=\frac{(t-\epsilon)f}{(t-\epsilon)-f}=\frac{(\frac{sf}{s-f}-\epsilon)f}{(\frac{sf}{s-f}-\epsilon)-f}=\frac{(sf-\epsilon(s-f))f}{f^2-\epsilon(s-f)}
が得られる。
ここで、s,f>>εなので、s±ε≈s, f±ε≈f, sf±ε2≈sfと近似できることを用いて、簡略化する。
s_n=\frac{(sf+\epsilon(s-f))f}{f^2+\epsilon(s-f)}=\frac{((s-\epsilon)(f+\epsilon)+\epsilon^2))f}{f(f-\epsilon)+\epsilon s}\approx\frac{sf^2}{f^2+\epsilon s}
s_f=\frac{(sf-\epsilon(s-f))f}{f^2-\epsilon(s-f)}=\frac{((s+\epsilon)(f-\epsilon)+\epsilon^2))f}{f(f+\epsilon)-\epsilon s}\approx\frac{sf^2}{f^2-\epsilon s}

これにε=Fδを代入すれば、被写界深度の近点と遠点を求める式になるのだが、フォトマスター検定の参考書では過焦点距離を用いた式になっているので、もう少し変形を進める。
過焦点距離とは、被写界深度の遠点が無限遠に届く、最短の撮影距離である。焦点深度と結像公式を用いて被写界深度の近点、遠点と同じ要領で、過焦点距離Hについても式を立てると、

となる。下の式からt=f+εが得られ、それを用いて上の式をHについて解くと、
H=\frac{tf}{t-f}=\frac{(f+\epsilon)f}{\epsilon}\approx\frac{f^2}{\epsilon}=\frac{f^2}{F\delta}
すなわち過焦点距離=レンズ焦点距離2÷(絞り値×許容ボケ)と求まる。これを用いてsn, sfをさらに整理する。
s_n\approx\frac{sf^2}{f^2+\epsilon s}=\frac{sf^2}{f^2+\frac{f^2}{H}s}=\frac{Hs}{H+s}
s_f\approx\frac{sf^2}{f^2-\epsilon s}=\frac{sf^2}{f^2-\frac{f^2}{H}s}=\frac{Hs}{H-s}

従って、
被写界近点=(過焦点距離×合焦距離)÷(過焦点距離+合焦距離)
被写界遠点=(過焦点距離×合焦距離)÷(過焦点距離−合焦距離)
である。

なお、過焦点距離の計算には許容ボケが必要になるが、許容ボケは通常、フィルムやセンサーの対角線長÷1300、が用いられる。
35mmフィルムなら、√(362+242)/1300≒0.03328 (≒1/30) mm、
フォーサーズ/マイクロフォーサーズなら√(17.32+132)/1300≒0.01665 (≒1/60) mm、である。


2016年06月05日

 角換わり棒銀の定跡形で悩む

昨日は毎年参加している将棋大会だった。
昨年はベスト4まで行ったのだが、今年は1回戦敗退だった。
筆者はこの所公私共に多忙で、ほとんど将棋を指していなかったので、予選突破できただけで上出来である。予選敗退した場合の抽選の指導対局に備えて駒落ちの定跡本を携えていたが、出番が無かった。

今年は、予選の2局と決勝の1局全て、筆者が苦手とする角換わりを仕掛けられ、全て棒銀で応対した。予選の2局はデタラメな攻めが都合良く炸裂して、一昨年に優勝し昨年準優勝した強敵(一昨年に1回戦で負けた相手)にも勝って大満足だったのだが、決勝の1回戦は、昨年予選で勝った相手に課題の多い負け方をしてしまった。

 図1

図1は角換わり棒銀の定跡である。筆者は角換わりには棒銀と決めているので、この局面には何度か遭遇している。1回戦は先手を持ってこの局面になったのだが、ここから後手は△1九角、▲2七飛、△1七歩成、▲同歩、△1八銀と指してきた(図2)。よくある手らしいが、筆者は初めて見た。

 図2

これに普通に▲2六飛と応対し、△2四歩に部分的な定跡通りに▲1二角と打った(図3)。

 図3

▲1二角と打つ時、もし△3三桂とされたらどうすれば良いんだろう、と不安に思いながら指したのだが、実際に△3三桂とされ、焦った。その後、自信無く、▲2四飛△2二歩▲1三香成と進めた。

△1九角〜△1八銀が入っていない、図1の形では、△2四同歩に▲1二角△2二金▲3四角成△3三金▲2四馬が定跡である。これだけは覚えていた。
激指14で解析すると、やはり図1からは定跡通り△2四同歩▲1二角△2二金〜▲2四馬が最善であり、▲1二角△3三桂には▲2四飛△2二歩▲2一角成として▲8三香を狙うのが良いらしい。しかし、本局の図3からはそうでなく、
・▲1二角△3三桂▲2四飛△2二歩には▲6八玉が最善(次善は▲4八金と本譜の▲1三香成)
・▲1二角△3三桂▲2四飛に△2二歩でなく△2三歩が最善
・▲1二角では▲2四飛が最善(△2三歩なら▲1四飛で飛車成りが防げない)
だったようだ。前の2つは難しいが、最後のは少し考えたらわかることである。次の△3三桂ばかり気にして、▲1二角以外の手を読まなかったのが失敗だった。

それでも、▲1三香成までにそれほどひどい手はない。▲1三香成の後、△2九銀成▲同飛△3七角成▲4八金△1五馬(図4)と進んだが、ここで絶好の機会を逃した。

 図4

香を打ったら馬が死ぬと勘違いして▲1六香と打ったら、△2五馬と寄られて空振りした。
ここでは▲1六銀で馬が死んでいたし、▲2四銀でも△2五馬なら▲3三銀が馬取りでひどいので馬を切る一手だっただろう。

実戦はこの後、お互いに怪しい手が多かったので省略するが、飛車をいじめられ、大悪手を指して挽回不能になってしまった。その攻める手は大悪手とわかっていながら、他に粘る手を全く思い付かなかったのである。激指で解析すると、色々粘る手はあったようだが、どれも筆者には理解できなかった。直接の敗因は▲6八玉を怠って飛車をいじめられたことだが、図4のチャンスを逃したのが運命の分かれ道だったと思う。
△2九銀成〜△3七角成とされる前にどこかで▲4八金と防ぐべきだったかも知れないが、△4五桂の当たりが強くなるので、やるなら△3三桂の前だと思うが、△3三桂の前には別に有効な手があったので、適当なタイミングが無かったと思う。

それより、図2から▲2六飛△3五銀とされた場合にどうすれば良いのかがわからない。感想戦では後手が自信無かったと言っていたが、激指14では△3五銀が最善であり、▲5六飛に△2九銀不成でも△4二金▲2三歩成でも後手やや良しと計算される。図1から▲5六飛まで先手に変化の余地はなく、もしそうなら図1は既に先手がまずいことになる。プロの棋戦には見つからなかったが、プロが図2の△1八銀を見落とすはずがない。プロも激指14も最善としない△1八銀に対策が見つからないとはどういうことなのだろう。
激指14の最善手は、▲2六飛△3五銀▲5六飛△2九銀不成▲4八金△5四桂▲7五歩という難解な手順である。△2九銀不成に▲5三飛成は次善手で、その後△5二金▲5六龍△3七角成▲6八玉△4四桂▲6六龍△1五馬で後手やや良しだそうだが、アマチュアとしては駒損でも龍ができて気持ちが楽になるので、これを選択すべきか。


2016年05月29日

 CppUTestでテスト駆動開発(1)

図書館で、「テスト駆動開発による組み込みプログラミング」という本に目を引かれ、流し読みしたら結構面白かったので、借りて帰ったのだが、筆者はこの所公私共に多忙で、ほとんど読めないまま返却期限を迎えてしまった。
とりあえず、その本で推薦されていた、CppUTestというユニットテストフレームワークを使ってみた。

筆者は、凄まじく高コストで生産性の低い日本企業のソフトウェア開発を数年間目の当たりにした後、21世紀に入った頃にテストファーストを含むExtreme Programmingの考え方に全面的に共感し、その時にCUnitというユニットテストフレームワークを使ったことがあるのだが、その価値がわからなかった。わざわざそんなのを使わなくても、ユニットテストを駆動する短いテストドライバを自分で書けば事足りると思ったし、実際、これまでそのようにしてきて困ったことが無い。

しかし、今回、この本でUnityやCppUTestの話を読むと、ユニットテストフレームワークには最先端の考え方が反映されており、この枠に嵌められてユニットテストを書いたりTDDをすれば、手軽に世界のトップクラスのインテリジェンスに触れられるような気がして、面白そうだと思った。

そこで、CppUTestを使ってみることにした。

書籍の第3章のLEDドライバの例を題材に、TDD(テスト駆動開発)方式で、まずは未実装のエラーが出る実行可能なテストを作ってみる。

LedDriver/LedDriverTest.cpp
#include "CppUTest/TestHarness.h"

TEST_GROUP(LedDriver)
{
  void setup()
  {
  }
  void teardown()
  {
  }
};

TEST(LedDriver, LedsOffAfterCreate)
{
  FAIL("Start here");
}
test_main.cpp
//from README.md
#include <CppUTest/CommandLineTestRunner.h>

int main(int ac, char** av)
{
   return CommandLineTestRunner::RunAllTests(ac, av);
}
Makefile
CXX = g++
CPPUTEST_HOME := $(HOME)/tmp/cpputest

#from README.md
CPPFLAGS += -I$(CPPUTEST_HOME)/include
CXXFLAGS += -include $(CPPUTEST_HOME)/include/CppUTest/MemoryLeakDetectorNewMacros.h
CFLAGS += -include $(CPPUTEST_HOME)/include/CppUTest/MemoryLeakDetectorMallocMacros.h
LD_LIBRARIES = -L$(CPPUTEST_HOME)/lib -lCppUTest -lCppUTestExt

TARGET = test_main
SRCS = test_main.cpp LedDriver/LedDriverTest.cpp
OBJS = $(SRCS:.cpp=.o)

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CXX) -o $@ $^ $(CXXFLAGS) $(LD_LIBRARIES)

%.o: %.cpp
#	GNU make implicit rule + "-o $@"
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

.PHONY: check
check: $(TARGET)
	./$(TARGET) -v

.PHONY: clean
clean:
	rm -f $(TARGET) $(OBJS) *.gcno *.gcov *~ */*~
	find . -name "*.gcda" | xargs rm -f

これでmake checkとすると、1つのテストが失敗し、"Start here"と表示される。
筆者は普段、テストの実行はmake testであるが、make checkが多数派らしいので、今後はそれに倣うことにした。(make testはPerlの世界に多い?)

MakefileにLedDriver.cのコンパイルが無いのは、TDDの、エラーにならない限りは作らない原則に従っているからである。

次に、LedDriver/LedDriverTest.cppにテストを追加し、それがpassするようにLedDriver.cを開発するのを繰り返す。
途中と詳細説明を省略するが、例えば途中段階では次のようになった。

LedDriver/LedDriverTest.cpp
#include "CppUTest/TestHarness.h"
extern "C" {
#include "LedDriver.h"
}

static uint16_t virtualLeds;

TEST_GROUP(LedDriver)
{
  void setup()
  {
    LedDriver_Create(&virtualLeds);
  }
  void teardown()
  {
  }
};

TEST(LedDriver, LedsOffAfterCreate)
{
	uint16_t virtualLeds = 0xffff;
	LedDriver_Create(&virtualLeds);
	LONGS_EQUAL(0, virtualLeds);
}

TEST(LedDriver, TurnOnLedOne)
{
	LedDriver_TurnOn(1);
	LONGS_EQUAL(1, virtualLeds);
}

TEST(LedDriver, TurnOffLedOne)
{
	LedDriver_TurnOn(1);
	LedDriver_TurnOff(1);
	LONGS_EQUAL(0, virtualLeds);
}

TEST(LedDriver, TurnOnMultipleLeds)
{
	LedDriver_TurnOn(9);
	LedDriver_TurnOn(8);
	LONGS_EQUAL(0x0180, virtualLeds);
}

TEST(LedDriver, AllOn)
{
	LedDriver_TurnAllOn();
	LONGS_EQUAL(0xffff, virtualLeds);
}
	
TEST(LedDriver, TurnOffAnyLed)
{
	LedDriver_TurnAllOn();
	LedDriver_TurnOff(8);
	LONGS_EQUAL(0xff7f, virtualLeds);
}

IGNORE_TEST(LedDriver, LedMemoryIsNotReadable)
{
	// TODO: let LED state read-only
}
LedDriver/LedDriver.h
#pragma once
#include <stdint.h>

extern void LedDriver_Create(uint16_t *address);
extern void LedDriver_Destroy(void);
extern void LedDriver_TurnOn(int ledNumber);
extern void LedDriver_TurnOff(int ledNumber);
extern void LedDriver_TurnAllOn(void);
LedDriver/LedDriver.c
#include "LedDriver.h"

enum {ALL_LEDS_ON = ~0, ALL_LEDS_OFF = ~ALL_LEDS_ON};

static uint16_t *ledsAddress;

static uint16_t convertLedNumberToBit(int ledNumber)
{
	return 1 << (ledNumber - 1);
}

void LedDriver_Create(uint16_t *address)
{
	ledsAddress = address;
	*ledsAddress = ALL_LEDS_OFF;
}

void LedDriver_Destroy(void)
{
}

void LedDriver_TurnOn(int ledNumber)
{
	*ledsAddress |= convertLedNumberToBit(ledNumber);
}

void LedDriver_TurnOff(int ledNumber)
{
	*ledsAddress &= ~convertLedNumberToBit(ledNumber);
}

void LedDriver_TurnAllOn(void)
{
	*ledsAddress = ALL_LEDS_ON;
}
Makefile
CXX = g++
CC = gcc
CPPUTEST_HOME := $(HOME)/tmp/cpputest

#from README.md
CPPFLAGS += -I$(CPPUTEST_HOME)/include
CXXFLAGS += -include $(CPPUTEST_HOME)/include/CppUTest/MemoryLeakDetectorNewMacros.h
CFLAGS += -include $(CPPUTEST_HOME)/include/CppUTest/MemoryLeakDetectorMallocMacros.h
LD_LIBRARIES = -L$(CPPUTEST_HOME)/lib -lCppUTest -lCppUTestExt

TARGET = test_main
SRCS = test_main.cpp LedDriver/LedDriverTest.cpp
CSRCS = LedDriver/LedDriver.c
OBJS = $(SRCS:.cpp=.o) $(CSRCS:.c=.o)

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CXX) -o $@ $^ $(CXXFLAGS) $(LD_LIBRARIES)

%.o: %.cpp
#	GNU make implicit rule + "-o $@"
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

%.o: %.c
#	GNU make implicit rule + "-o $@"
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

.PHONY: check
check: $(TARGET)
	./$(TARGET) -v

.PHONY: clean
clean:
	rm -f $(TARGET) $(OBJS) *.gcno *.gcov *~ */*~
	find . -name "*.gcda" | xargs rm -f

make checkすると、"OK (7 tests, 6 ran, 6 checks, 1 ignored, ...)"となる。
ignoredなのは「実行可能なリマインダ」である。

ところで、CppUTestには簡単なメモリリーク検出機能がある。各TEST()の実行前にTEST_GROUPのsetup()が、実行後にteardown()が呼び出されるが、そのsetup()前とteardown()後とでメモリ確保状態を比較するようである。
試しに、LedDriver/LedDriver.cのLedDriver_TurnAllOn()を

#include <stdlib.h>
void LedDriver_TurnAllOn(void)
{
	void *p = malloc(1);
	*ledsAddress = ALL_LEDS_ON;
}
に変えてmake checkとすると、次のエラーメッセージが出た。
TEST(LedDriver, AllOn)
LedDriver/LedDriverTest.cpp:46: error: Failure in TEST(LedDriver, AllOn)
	Memory leak(s) found.
Alloc num (6) Leak size: 1 Allocated at: LedDriver/LedDriver.c and line: 35. Type: "malloc"

うまく使えば便利そうである。
CppUTestに触れてみて、TDDをきちんと勉強しようと思った。

続きを読む "CppUTestでテスト駆動開発(1)"

2016年05月04日

 "Sum and Product Puzzle"をPythonで解いてみた

昔、多分NetNewsのfj.sci.mathかどこかだと思うが、次のような問題が流れていた。

司会と A さん, B さんがいます.
司会 「今から Joker を除いたトランプ 52 枚から無作為に 2 枚を取り出し, A さんにはその二数の積を, B さんには和を教えます. その数から元の二数が何であるかを考えてください」
A, B 「はい」
司会 「(B さんにわからないよう, A さんに積を教える)」
司会 「(A さんにわからないよう, B さんに和を教える)」
A さん 「教えてもらった数からは元の二数はわかりません」
B さん 「私もわかりません. でも, A さんが『わからない』と言うのはわかっていました」
A さん 「それなら私はわかりました」
B さん 「それなら私もわかりました」
さて, A さん, B さんの聞いた数はそれぞれいくつでしょう?
これだけで答えが1つに特定できることが驚きであり、取ってあった。それをたまたま見つけたので、もう一度やってみた(解説は後述)。
一見どこから手を付ければ良いのかわからないが、「A さんが『わからない』と言うのはわかっていました」からBさんの数字を考えてみると、ほぼ答えまで絞られるので、解くのにそれほど時間はかからない。

NetNewsの元の投稿は探せなかったが、その代わりに、この問題の元ネタが見つかった。 元の問題は1969年に発表されたもので、その後に様々なバリエーションが作られ、一連の問題は"Sum and Product Puzzle"と呼ばれているらしい。参考文献[1]によると、1979年にMartin Gardnerが"the Impossible Puzzle"と呼んで紹介した頃の英語版の1つは次のようなものだったらしい。

Two numbers m and n are chosen such that 2 ≤ m ≤ n ≤ 99. Mr. S is told their sum and Mr. P is told their product. The following dialogue ensues:
Mr. P: I don’t know the numbers.
Mr. S: I knew you didn’t know. I don’t know either.
Mr. P: Now I know the numbers.
Mr. S: Now I know them too.

In view of the above dialogue, what are the numbers?
元の2数の範囲が2〜99なだけで、後は全く同じである。おそらく同じようにして解けるのだが、範囲が広い為に格段に面倒である。
どうせ、部分的にヒューリスティックに絞り込んでいるが、しらみ潰しに探しているのと大差無い。おそらく、整数の範囲を制限しているので、しらみ潰しに探す以外に解法は無いと思う。(例えば「2より大きいあらゆる偶数は素数の和で表せる」というゴールドバッハ予想も、整数の範囲を制限しているので、単独では使えない。m+nとしてあり得る188は7+181 = 31+157 = 37+151 = 61+127 = 79+109であり、99以下の素数の和ではないので、m,nが共に素数である組み合わせが無い。)
プログラミングの勘を取り戻す為のリハビリを兼ねて、プログラムを作ってしらみ潰しに探すことにした。

#!/usr/bin/python

MIN = 2
MAX = 99

def good_factors(p):
    """Generates pairs of factors of p"""
    return [(m,n) for m in range(MIN, MAX+1) for n in range(MIN, MAX+1) if m <= n and m*n == p]

def good_summands(s):
    """Generates pairs of summands of s"""
    return [(m,n) for m in range(MIN, MAX+1) for n in range(MIN, MAX+1) if m+n == s and m <= n]

def fact1(m, n):
    """Mr. P: I don't know the numbers."""
    return len(good_factors(m*n)) > 1

def fact2(m, n):
    """Mr. S: I knew you didn't know."""
    for (m_, n_) in good_summands(m+n):
        if not fact1(m_, n_):
            return False
    return True

# This is not necessary but just writing straightforward
def fact3(m, n):
    """Mr. S: I don't know either."""
    return len(good_summands(m+n)) > 1

def fact4(m, n):
    """Mr. P: Now I know the numbers."""
    fact2_compliers = [(m,n) for (m,n) in good_factors(m*n) if fact2(m,n)]
    return len(fact2_compliers) == 1

def fact5(m, n):
    """Mr. S: Now I know them too."""
    fact4_compliers = [(m,n) for (m,n) in good_summands(m+n) if fact4(m,n)]
    return len(fact4_compliers) == 1

result = [(m,n) for m in range(MIN, MAX+1) for n in range(MIN, MAX+1) if m <= n and fact1(m,n) and fact2(m,n) and fact3(m,n) and fact4(m,n) and fact5(m,n)]

print "result =", result

これを実行すると、(m,n)の正解である(4,13)が出力される。
MIN = 1, MAX = 13にすると、冒頭のトランプの問題の元の2数も計算できる。

但し、心持ち関数型プログラミングっぽくしてみたが、これだととても時間がかかる(Intel Core i5 1.6GHzのCPUでも2分以上)。次のように、good_factors()とgood_summands()が計算結果をキャッシュするように書き換えると、20倍くらい速くなった。

good_factors_dict = {}
def good_factors(p):
    """Generates pairs of factors of p"""
    if not good_factors_dict.has_key(p):
        good_factors_dict[p] = [(m,n) for m in range(MIN, MAX+1) for n in range(MIN, MAX+1) if m <= n and m*n == p]
    return good_factors_dict[p]

good_summands_dict = {}
def good_summands(s):
    """Generates pairs of summands of s"""
    if not good_summands_dict.has_key(s):
        good_summands_dict[s] = [(m,n) for m in range(MIN, MAX+1) for n in range(MIN, MAX+1) if m+n == s and m <= n]
    return good_summands_dict[s]

◆参考文献
[1] "Sum and Product in Dynamic Epistemic Logic"
H. P. van Ditmarsch, et al.
J Logic Computation (2008) 18 (4): 563-588
First published online: December 21, 2007
http://www.cs.otago.ac.nz/staffpriv/hans/sumpro/sumandproduct06.pdf

続きを読む ""Sum and Product Puzzle"をPythonで解いてみた"

2016年03月13日

 3手目角交換からの▲4五角について

10年以上前だろうか、Yahoo!将棋やハンゲームでネット将棋をよく指していた頃、後手を持って、▲7六歩に△3四歩とすると、いきなり角交換をされて▲4五角と打たれることがよくあった。

これである。本当によくやられた。流行だったのだろうか。

筆者は、これをされるのが本当に嫌だった。
といっても、よく負けたからではない。むしろ同程度のレーティング(アマ三段程度)の相手によく勝った。後手を持って勝率は6割を超えていたと思う。

何故嫌だったかと考えると、この手は自ら損をしに行く無謀な手だと思っており、それに勝っても嬉しくないからだった。
この後に特に気を付けるべき手があった経験は無く、つまりハメ手でもなく、しかも先手が角を手放してまで入手した一歩がものを言う展開になったこともほとんどなく、単に混沌とした展開に持ち込んで相手のミスを誘うだけの、乱暴な手だと思っていた。

昔からあるこの手に有名な定跡が見当たらないのも、この▲4五角にて後手良し、が共通見解だからだと今でも思っている。自ら不利な状況を選択する手で、その先に有効な手が無さそうであれば、対策を研究する必要があまり無い。何せ、ミスしなければ勝てるのだから。
▲7六歩に△4四歩(ハメ手なのだろうか、年配の知人で何度負けてもこれを愛用する人が居た)(追記:パックマンという名が付いているらしい)ほど無謀だとは思わないが、そういうことをされると、勝って当然で負けたら悔しい、指すだけ損の将棋になるので、やる気を無くしたのである。

昨年、図書館で「イメージと読みの将棋観」(日本将棋連盟)という本を借りたら、プロにこの▲4五角をどう思うかを尋ねた結果として、総じて先手不利と書かれており、やっぱりな、と思った。
そんな中、この間、実家で、昔買って読んでなかった将棋の本の中に、この▲4五角をテーマにした本があるのを見つけた。「筋違い角と相振り飛車」(木屋太二著 森内俊之監修)という本である。
これまで、筋違い角というのは、下の図のように、5四や5六に角を打つことだと思っていた。

この本を読んで、まず、3手目角交換からの▲4五角も筋違い角と呼ぶことに驚いた。(筆者だけか)
続いて、この▲4五角がプロも監修するような立派な戦法であることに驚いた。

調べてみると、5手目▲4五角の筋違い角はプロの公式戦も1990年以降に50局以上あるので、プロ的にも完全に無い手ではなさそうである。ただ、勝率は後手が6割くらい勝っている。

この本は筋違い角側の視点で書かれており、▲3四角の後に▲1六角と引いて大体筋違い角有利となるので、筋違い角を仕掛けられる側としてはあまり参考にならなかった。


この機会に、これまでに考えたり読んだりした、3手目角交換からの▲4五角の対策を思い出してみた。
3手目角交換からの▲4五角を頻繁にやられた時期に最初に考えたのは、▲4五角に対して△5二金右▲3四角△6五角であった。

歩損がなく手得なので、悪いはずがない。実際、当時はほとんどこのように指して、まあまあ勝てたと記憶している。

しかし、一歩得の代償に角を手放すのは損だと信じると、もっと良くできるはずである。そう思って調べて知ったのが、▲4五角に対して△5二金右▲3四角△6四歩▲8八銀▽6五歩である。

△6四歩に▲6六歩とすると△6五歩▲同歩△6六角があるので、▲6六歩とできないのがミソである。
この筋違い角は、▲6六歩とできないと角が狭いので、これで角を攻める展開になれば、後手が指しやすそうである。確か、以前は決定版と言われていた対策だったと思う。
ただ、筆者はあまりこの対策で勝てた記憶が無い。もろに筋違い角を指す人の研究にはまるからだろうか。上述の「筋違い角と相振り飛車」にも後手のこの対策を撃破する手順が複数書かれているので、それを食らっていたのかも知れない。

一度やってみたいと思っているのは、▲4五角に対して△6二銀▲3四角△3二金▲6六歩△3三銀▲7八角△8四角である。

これを▲6八飛と受けると、△9五角で王手飛車が掛かるのである。

それでも先手が一歩得なので後手有利とは言い切れないが、アマチュアレベルでは序盤の飛角交換は飛車有利ではないだろうか。

しかしながら、上述の「イメージと読みの将棋観」に載っていた、藤井九段の腰掛銀+△4五歩が最強の対策かなという気がする。

これは相当に先手が動きづらそうである。

手順の一例は、▲7六歩△3四歩▲2二角成△同銀▲4五角の後、
△6二銀 ▲3四角 △3二金 ▲6六歩 △6四歩 ▲8八銀 △6三銀 ▲7七銀
△5四銀 ▲6八飛 △4四歩 ▲6七角 △4五歩
である。△4五歩を阻止するには先に▲4六歩とする必要があるが、先手は角打ちの隙を与えずに▲4六歩とすることが難しいので、実現しやすそうな対策である。

続きを読む "3手目角交換からの▲4五角について"