RAS Syndrome

冗長。

ボードゲームはプログラミングしやすい

前回の記事でゲームについて考えた。

ikngtty.hatenablog.com

結論だけ抜き出そう。

ゲームとは、 「プレイヤーが、以下のルールに沿って、アクションを選択し、結果を決める営み」 である。

// ルール1: 初期状態
def initialState(): GameState

// ルール2: アクション定義
def createAction(args: Any): GameState => GameState

// ルール3. 可能/禁止アクション
def isAvailable(action: GameState => GameState, state: GameState): Boolean

// ルール4. 結果判定
def getGameResult(state: GameState): GameResult

// ルール5. ゲームの状態のアクセシビリティ
def getGameStateInformation(state: GameState, player: Player): GameStateInformation

今回は、ボードゲームをプログラミングに落とし込むことについて少し考えてみる。

ゲームについてプログラミングでできること

以下が考えられるように思う。

機能1. ゲームの状態の管理

ボードゲームで言えばボードの代わりである。 ルールの演算などは全てプレイヤーに任せ、コンピューターは関知しない。 真っ白なキャンバスとして使うようなものだ。 アクションの可能/禁止も勝ち負けも気にせず、自由に状態を弄れる。

例えば詰将棋を作る時などは、この機能だけの方が都合が良いだろう。

機能2. ルールの施行

審判や、いわゆるゲームマスターのような役割を担う。 開始状態をセットアップし、プレイヤーの可能なアクションのみを受け付け、勝敗を判定する。

例えば将棋のオンライン対戦アプリなどは、機能1と2の組み合わせと言える。

機能3. プレイヤーの代行

いわゆるCPUプレイヤーだ。

例えばCPU対戦ができる将棋アプリは、機能1, 2, 3の組み合わせとなる。 また、CPU同士で戦わせて棋譜を量産しながらCPUのAIを強化する、なんてのは最近のトレンドだろう。

プログラミングが難しい/向いていないパターン

思いつくままに挙げてみる。 尚、私が個人で開発することを想定した難易度判定なので、最先端の商業ゲームが普通に実現しているようなことも挙げる。

アクションが肉体を使った物理的なものである

スポーツ全般が当てはまる。

肉体の操作は基本的にパラメータが非常に多く、情報量が多い。 まず、肉体の操作を正確にコンピューターにインプットさせるのも大変だ。 最低限、kinectだとかswitchのJOYコンだとかが必要になる。 インプット後も、物理演算等で計算量は多い。

代わりに情報量を減らすと、今度は再現度が減り、物理世界で行うゲームとは段々かけ離れていってしまう。 例えば、CLIの腕相撲ゲームを想像してみてほしい。 「相手は80kgの力で押してきています。あなたは何kgの力で返しますか?キーボードで数値を入力してください。」 紛うことなきクソゲーである。

また、CPUとの対戦は、やりがいが無いかもしれない。 何せ相手の力が80kgが800kgかはマシンパワーと設定変数次第なのだ。 こちらが苦労して何年も筋トレしたところで、CPUは一瞬のチューニングで追い抜いてしまう。

アクションがトークである

テーブルトークゲームというジャンルだろうか。 人狼やキャット&チョコレートなどが当てはまる。

機能1は簡単だ。 チャットでのやり取りをそのまま表示すれば良い。

機能2も簡単である。 ゲームマスターの真似をして、「それではみなさん投票してください。」と問いかけるだけだ。 しかしそれは即ち、大事な判定処理を人間に委譲するということである。 結果、コンピューターは大してやることが無い。 簡単だが効果が薄いのである。 コンピューターの最も得意なことは計算であるが、ここでは計算処理は出てこない。

では、判定処理をCPUが行うとどうなるか。 例えばキャット&チョコレートで言えば、説得力をCPUが判定するのである。 すると途端に難易度が跳ね上がる。 自然言語処理をした上で、人間の感情などを扱わなければならない。

また、機能3も非常に難易度が高いことは説明不要だろう。

アクションが絵である

「アクションがトークである」に近いが、プレイヤーの入力がめんどくさくなりやすいことも挙げられる。

ゲームの状態が複雑である

物理世界や現実世界を利用する類のゲームが当てはまる。 物理世界の難易度は「アクションが肉体を使った物理的なものである」でも述べているので、 ここではそれ以外の現実世界に関係するゲームを考える。

例えば、「ツイッターで先に100RT稼いだ方が先」みたいなゲームが当てはまる。 実際にツイートさせて測定できるなら簡単だが、コンピューターがRT数をシミュレーションしなければいけないとしたら大変だ。 フォロワー数、各フォロワーの趣味・嗜好、フォロワーとの関係性、世の中のトレンド、自身のツイート履歴による文脈、 などなど挙げだしたらキリがない。

また、仮想世界における政治シミュレーションゲームなども、リアルにすればするほど状態変数が増える。 かといって単純にするとクソゲーになる。

アクションにリアルタイム性が求められる

のは、実はそんなに難しくない。 例えば以下ではリアルタイム性のある早押しクイズをシミュレートしている。

早押しクイズでNode.jsの非同期処理とかイベント駆動とかを試す

まあでも、ターン性だともっと簡単なのは確かだ。

ボードゲームは簡単か

ボードゲーム(board game)とは、ボード(盤)上にコマやカードを置いたり、動かしたり、取り除いたりして遊ぶゲームの総称。盤上ゲーム盤上遊戯とも呼ばれる。

ボードゲーム - Wikipedia

とのことである。

ボード上の駒を指で弾いて相手のゴールにシュート!みたいなゲームだと大変だが、そうでない限り物理が出てくることは少ない。 サイコロやルーレット等は物理と言えば物理だが、これはランダムな値をコンピューターが出すだけで問題無いだろう。

ゲームの状態は基本的にボードで表現できる範囲の静的な情報しかない。 アクションもほとんどがコマやカードの操作だ。パターンは限られる。

総じて情報量が少ないのがボードゲームの特徴だ。 加えて、ターン性であることも多い。

上記のプログラミングが難しいパターンはほとんど回避できる。 また、ほとんどのボードゲームにおいて、先の展開を読んだ上での戦略的決定が重要となる。 そのため、コンピューターの演算能力が発揮できる場面は多い。

結論

ボードゲームは個人の趣味プログラミング対象としてとてもコスパが良い。 まあ、特に考えなくてもなんとなく分かっていたことではある。 だからなんだという感じだ。 なんだったんだろうこの考察。

おしまい。

ゲームとは何か

趣味の一環で、ボードゲームに関するプログラムを書いた。

書きながら、「色々なボードゲームに汎用的に適用できる書き方はどんなだろう」と思考を巡らせ続けた。 あまりに凝りすぎて、「ボードゲームとは何なのか?」「そもそもゲームって何?」ってところまで考える羽目になった。

ゲームといっても色々だ。 デジタルなテレビゲームもあれば、アナログなボードゲームもある。 スポーツだってゲームな気がするし、鬼ごっこも多分ゲームだ。 私が学生時代に友人とよくやっていた、「1から20の中から好きな数を同時に言い合い、一番大きかった人が勝ち」というゲームだってゲームだろう。

学術的な定義とかは、おそらくもう決まっている。 だが、私自身はそんな定義を知らなくてもなんとなく、「これはゲームだ」「これはゲームじゃない」と判断している。

今回はその感覚を分析し、整理し、"個人的なゲームの定義"としてまとめてみた。

以下は目次。

要素の洗い出し

ゲームに一番必須な要素とは何か。

おそらくプレイヤーでは無いだろうか。

じゃあプレイヤーが集まって何かわちゃわちゃやってたら全部ゲームだろうか。 そんなことは無い。 プレイヤーは皆、ルールに基づいて行動している。

ルールとは何だろう。 基本的にまずは、「何をして良いか」「何をしてはいけないか(又は、何をしたらペナルティが与えられるか)」だと思う。 一言で言えば、可能/禁止アクションといったところだろう。

さて、ここまでの定義で言えば"おままごと"もゲームに該当することになる。 しかし、私には"おままごと"がゲームであるようには思えない(人によって違うかもしれない)。 なぜなら、"おままごと"には勝敗が無いからだ。

では、勝敗を競うことがゲームの必須条件だろうか。 これもまた違うように思う。 例えば賭け麻雀は、何位になるか以上に「何点勝つか」「何点負けるか」をプレイヤーは意識する。 また、一人用ゲームでは競う相手が居ないが、それでも「クリアできたか」とか「クリアまでのタイム」だとかをプレイヤーは意識する。

要するに、必要なのはゲームに対する明確な結果の定義なのだ。 そしてこれはルールで定めるべきもう一つの大事な要素でもある。

プレイヤーはより良い結果の為に競いあう。 中には、「結果よりも楽しむ過程が大事」というプレイヤーがいたり、勝つことよりゲームを混乱させることを優先するトリッキーなプレイヤーもいたりするだろう。 しかし、だからといって結果の定義が無くてもいい訳では無い。 結果の定義がなければ、大多数のプレイヤーはアクション選択の指針を失い、ゲームはほとんど成立しなくなる。

尚、結果が必須だとしてしまうと、"おままごと"と同様に『どうぶつの森』などもゲームでは無いということになってしまう。 しかし私はそう言ってしまっても良いかなと思っている。 『どうぶつの森』は確かにテレビゲーム機をプラットフォームとしているが、趣旨は「バーチャル空間での生活体験の提供」だ。 私にとってそれは狭い意味での"ゲーム"では無い。 (一応注釈しておくが、『どうぶつの森』の価値を貶める為に言っているのでは無い。『どうぶつの森』は楽しい。)

要素の整理

ここまでで出てきたキーワードを並べよう。

  • プレイヤー
  • アクション
  • ゲーム結果
  • ルール
    1. 可能/禁止アクション
    2. 結果判定

ルールを少し深掘りする。

ルール1. 可能/禁止アクション

とはつまり、アクションに対し可能か禁止か判定する規則のことだ。 つまり、アクションに対しTrue or Falseのブール型を返す関数として表現できる。

何かプログラミング言語で表現してみよう。 私が知っている中で型の示し方が一番綺麗なのがScalaなので、Scalaで書いてみる。 知らない方でも雰囲気は伝わるだろう。

尚、私のScala歴は10分ぐらいだ。

def isAvailable(action: Action): Boolean

すると、これでは足りないことに気付く。 よく考えれば、Actionが可能かどうかはそれ単体では判断できないのだ。全てはゲームの状況次第である。 即ち、ここまでのアクションの積み重ねを考慮し、現在のゲームの状況を算出してから検討する必要がある。

def isAvailable(currentAction: Action, actionHistory: Array[Action])

とりあえずこれで進む。*1

ルール2. 結果判定

同様にScalaで表現する。

def getGameResult(actionHisotry: Array[Action]): GameResult

これもゲームの状況を算出する必要がある。算出したらそれに対し、ゲームの結果を返す。 上記の通り、結果は単純な勝敗とは限らないので、ゲームの種類に応じてGameResultは柔軟に形を変えることになる。

この返し値が、nullとか、NullObjectとか、"結果が出ない事を示すオブジェクト"とかであった場合に、ゲームが続行されるイメージだ。

説明変数「ゲームの状態」を導入

さて、上の手順において優秀なプログラマーの方々は、「ゲームの状況を算出する」という処理を早く共通化したくてうずうずしていることだろう。 せっかくなのでやってみる。 あまり深い意味はないが、呼び方をゲームの状態に変える。

// ルール3. ゲームの状態の算出法
def getGameState(actionHistory: Array[Action]): GameState

// ルール1. 可能/禁止アクション
def isAvailable(action: Action, state: GameState): Boolean

// ルール2. 結果判定
def getGameResult(state: GameState): GameResult

以下の図式となる。

旧ルール1 = 新ルール1 + 新ルール3
旧ルール2 = 新ルール2 + 新ルール3

これで各ルールは、より現実に即した形となった。

例えば将棋で、「1手目はこう指して、2手目はこう指して、...、71手目にこう指した時(=アクション履歴)は先手勝ちですか?」という聞き方は普通しないだろう。 それよりは、盤面(=ゲームの状態)を指し示して、「先手番でこうなったら先手勝ちですか?」と聞くのが普通だ。 つまり、ルール2.はゲームの状態を引数とする方が普通というわけだ。 この場合、ルール3.として「1手目はこう指して2手目は...」がどのような盤面に繋がっていくのかを追加で定義しなければならない。

注意したいのは、これはただのリファクタリングだということだ。 ルールの使い勝手がよくなったというだけで、これらのルールで表現できるゲームの幅が広がったというわけではない。

新ルールで表現されたゲームを旧ルールで表現し直すには、今のリファクタリングの逆を行えば良い。 例えばオセロなら、

## ルール1. 可能な手
8x8マスのボードがあるとする。(マス目の表記法については省略。)
はじめにe4d5に黒石、d4e5に白石が置いてあるとする。
石が置かれると、同じ色で挟まれた石は、挟んだ石と同じ色に変わる。

棋譜を「置かれた石の位置の履歴」と解釈し、上記規則に則ってボードに石が置かれたと想定すると、手番プレイヤーは以下の手を打つことが可能である。(手番の説明は省略。)

* 自色の石を置くと敵石の色を挟めるような位置を宣言する。(又は、ボードがある場合そこに石を置き、ボードの状態を上記規則に従って変更する。)

## ルール2. 結果判定
8x8マスのボードがあるとする。(マス目の表記法については省略。)
はじめにe4d5に黒石、d4e5に白石が置いてあるとする。
石が置かれると、同じ色で挟まれた石は、挟んだ石と同じ色に変わる。

棋譜を「置かれた石の位置の履歴」と解釈し、上記規則に則ってボードに石が置かれたと想定すると、以下のように勝敗を判定することができる。
* ボードに石が置かれていないマスがある場合、勝敗未決(試合続行)。
* 上記以外の場合、自色の石がより多くボードに置かれていた方のプレイヤーが勝ち。

というWETなルールとなる。 まるでボードを実際に使ってはいけないかのような、脳内オセロ限定みたいな書き方になった。 (しかしボードを置いてはいけないとは言っていない。普通のオセロにも対応できる。)

今やったことはまるでトランスパイルである。 ES6のコードをES5で表現し直しているようなものだ。 しかし、ES6とES5でできることは本質的には変わらない。 それと同じだ。 新ルールは旧ルールと比べ、シンタックスが増えて便利になっただけなのである。 表現の幅は本質的には変わっていない。

まあ、だからどうしたということは特に無いのだが。 そんなわけでとりあえず、リファクタリングを更に進める。

アクションの再解釈

これまでアクションの定義は特にしなかった(=プリミティブな型として扱っていた)。 しかしゲームの状態という変数を導入した今、アクションの意味合いが表現できるようになる。 アクションとは、ゲームの状態を変更するもの。 つまり変更前のゲーム状態に対して変更後のゲーム状態を返す関数だ。

def action(state: GameState): GameState

ちなみにGameStateはイミュータブルなものとして考えている。 GameStateがミュータブルで、自由に書き換えていいのなら、別に値を返す必要はない。

さて、ゲームによってアクションは色々考えられる。 将棋なら「76歩」もあれば「投了」もある。 ボクシングなら「右手を相手のこめかみに向かって右上水平角30°から時速40kmでぶつける」等といくらでも複雑になるだろう。

とりあえず、ゲーム毎にアクションのバリエーションはある程度決まってくる。 ここではアクションの生成法を定義することで、アクションに固有の形式を与えてみよう。

def createAction(args: Any): GameState => GameState

アクションの型は基本的にGameState => GameStateな関数なのだが、実際はGameState => GameStateの中でもある一定のパターンしかあり得ない。 それをこの高階ファクトリ関数によって示している。 これはつまり、より狭い意味での型を定義している、ってことになるんじゃないかなぁと思ったり思わなかったり。 ファクトリ関数が型の役割をするというのは、JavaScriptの発想から影響を受けた。

まあしかし、この辺は言語によって表現方法が変わってくるだろう。 関数をファーストクラスとして扱うのが難しい場合は、アクションをオブジェクトにしてしまっても良い。 っていうかその方が綺麗な気もする。 以下は、「関数っぽい役割だけど実際はオブジェクト」という形でアクションを実装したRubyの例。

class Action
  def applyTo(state)
    raise 'Not implemented.'
  end
end

class AddSomethingAction < Action
  def initialize(something)
    @something = something
  end

  def applyTo(target)
    target + @something
  end
end

add5 = AddSomethingAction.new(5)
puts add5.applyTo(100)  # -> 105

ちなみにPythonだと、__call__メソッドをオーバーライドすることで、関数っぽく呼べるオブジェクトが作れる。 おそらくこれが最も綺麗だろう。

さて、これでアクションは、どういう状態変化を起こせるかを自身に定義できるようになった。 以前はこれらの定義は、ルール3の中で表現される想定であった。

// ルール3. ゲームの状態の算出法
def getGameState(actionHistory: Array[Action]): GameState

今やルール3の中身は、全てアクションの定義に委譲されている。

// ルール3-1: 初期状態
def initialState(): GameState

// ルール3-2: アクション定義
def createAction(args: Any): GameState => GameState

// ルール3. ゲームの状態の算出法
def getGameState(actionHistory: Array[GameState => GameState]): GameState = {
  actionHistory.foldLeft(initialState())((state, action) => action(state))
}

Scalaの実装に立ち入り過ぎて、さすがに勉強時間の追加を強いられた。 まあしかし、言いたいことは 「初期状態にアクション履歴内のアクションを全部順番に適用すれば、今のゲームの状態が得られるよ。」 ということだけだ。 もちろん、図式は以下のようになる。

ルール3 = ルール3-1 + ルール3-2

というわけで、ルールを分解して番号を振り直し、新体系としてまとめ直そう。

// ルール1: 初期状態
def initialState(): GameState

// ルール2: アクション定義
def createAction(args: Any): GameState => GameState

// ルール3. 可能/禁止アクション
def isAvailable(action: GameState => GameState, state: GameState): Boolean

// ルール4. 結果判定
def getGameResult(state: GameState): GameResult

先程と同様、こちらもリファクタリングに過ぎない。

物理ルールの存在

ルール2(アクション定義)について。 例えばジェンガについて考えてみよう。

def createAction(args: 力加減とか): GameState => GameState = {
  // 力加減が弱い場合:ジェンガの山がよほどグラグラしてない限り崩すことはないような関数を返す
  // 力加減が強い場合:どんなに安定していたジェンガの山もあっという間に崩すような関数を返す
}

みたいなイメージだ。 しかし、ジェンガの説明書にこんなことは書いていない。 書いてあるのは「抜いて崩れたら負け」ということだけだ。 これはつまり、「ジェンガを抜く」というアクションによって「ジェンガの山」という状態がどう変化するかを、物理法則に一任しているのである。

今回の思索の最終的な目標は、定義したゲームのルールをプログラムに落とし込むことである。 なので、物理空間では自明のことであっても、サイバー空間において自明でないことは全て明記する必要がある。 具体的に言えば、物理エンジンを実装しなければいけない。

これを踏まえた時に、特に注意しなければいけないのはゲームの状態の時間変化だ。 プレイヤーがアクションをせずとも状態は勝手に推移してしまうのである。 そのため、先程使用した以下の前提が使えなくなる。

// ゲームの状態の算出法
def getGameState(actionHistory: Array[GameState => GameState]): GameState = {
  actionHistory.foldLeft(initialState())((state, action) => action(state))
}

まあしかし、この辺を完全に反映させるのは骨が折れる。 というか折れたのは私の心です。 この辺の明記は今回は諦めることにする。

追加ルール「ゲームの状態のアクセシビリティ

物理法則について色々なパターンを考えてみると、その中の一つに 「伏せられているカードは見えない」 「相手の手札(手牌)は見えない」 というような、認知に関する物理法則があることに気付く。

プレイヤーが何を認知できるかは、ここまで見落としていた大事なファクターだ。 将棋やオセロ等は完全情報ゲームなので関係ない話だが、麻雀やトランプにおいては欠かせない。

一言で言うと、ゲームの状態のアクセシビリティである。 以下のように定義してみよう。

def getGameStateInformation(state: GameState, player: Player): GameStateInformation

PlayerGameStateにアクセスことはできない。 代わりにgetGameStateInformation関数を通し、GameStateInformationを得ることができる。

尚、これは実装面の話になるが、GameStateInformationGameStateと同じインターフェースにするのが良さそうだ。 プレイヤーがアクセスできない情報はnullにしたりNullObjectにしたり、アクセスするとエラーを吐くような仕組みにする。 これにより、GameStateGameStateInformationも今までのルール関数を共通で使用することができるようになる。

追加しないルール「ゲームフロー

ボードゲーム等の説明書では、よくはじめに「ゲームの流れ」と言うものがある。 これは私のゲーム定義には載せないことにする。 なぜならこれまでの定義で、ゲームの流れ(以下、ゲームフロー)は表現できるからだ。

例えば、もう一度オセロのルールを定義してみよう。

## ルール1: 初期状態
8x8マスのボードがある。(マス目の表記法については省略。)
e4d5に黒石、d4e5に白石が置いてあるとする。
**黒番である。**

## ルール2: アクション定義
* 自色の石を1つ置く。
この際、同じ色で挟まれた石は、挟んだ石と同じ色に変わる。
**また、白番の場合は黒番に、黒番の場合は白番に変わる。**
* パスする。

## ルール3. 可能/禁止アクション
* **自分の手番でない場合、取れるアクションは無い。**
* 敵石の色を挟めるような位置にしか自石を置けない。
* 自石を置けない場合のみ、パスをすることができる。

## ルール4. 結果判定
* **ボードに石が置かれていないマスがある場合、勝敗未決(試合続行)。**
* 上記以外の場合、自色の石がより多くボードに置かれていた方のプレイヤーが勝ち。

## ルール5. ゲームの状態のアクセシビリティ
プレイヤーはボードを自由に見て良い。

以前と比べ、随分と整理できているように感じる。まあそれは置いといて。

オセロのゲームフローは以下の通りだろう。

1. 黒が打つ
2. 白が打つ
3. ボードに石が置かれていないマスがある場合、1.へ。そうで無い場合、勝敗を判定する。

このフローは全て、上のルール定義の強調部分から完全に読み取れる情報である。 というわけで、ゲームフローはゲームの状態等と同じく説明変数にすぎない。 リファクタリングと称してまた括り出してもいいが、今回は特に意義を感じないのでなんとなくやらないことにする。

結論

ゲームとは、 「プレイヤーが、以下のルールに沿って、アクションを選択し、結果を決める営み」 である。

// ルール1: 初期状態
def initialState(): GameState

// ルール2: アクション定義
def createAction(args: Any): GameState => GameState

// ルール3. 可能/禁止アクション
def isAvailable(action: GameState => GameState, state: GameState): Boolean

// ルール4. 結果判定
def getGameResult(state: GameState): GameResult

// ルール5. ゲームの状態のアクセシビリティ
def getGameStateInformation(state: GameState, player: Player): GameStateInformation

おわり。

*1:オブジェクト指向信者の方は、早くisAvailableをActionクラスのメソッドにしたくてうずうずするかもしれない。 しかし今はまだ色々と整理中の段階だ。その辺の最適化は待っていただきたい。

ただ、Action型の中に色々とアクションについての情報を示す属性が入っていることはこの段階で想定している。 例えば、player(誰のアクションか)、timing(ゲーム開始後何秒後に行われたアクションか)等が考えられる。

このような単純な属性の整理は"データ型"の設計であり、メソッドを含めた"クラス"設計よりも前の段階で行えると考えている。 具体的に言えば、以下のような手順を踏むのがクラス設計への基本的な道のりだと私は考えている。

  1. データ型の整理
  2. やりたい事を関数に分解しながら洗い出し
  3. 関数とデータ型をどう組み合わせてクラスとして固めるか決定

【スプラトゥーン2】立ち回りメモ

スプラトゥーン2やってます。
1はちょっとしか触ってないです。初心者です。

先日、スプラトゥーンをやったことがないという友人の家に遊びに行き、
「これがスプラトゥーンや!!おもしろいやろ!!!」
というのを見せつけるため、友人の前でガチマッチをやってみせました。
レベルはB帯です。

友人の反応はというと、
「そこ左潜って!オブジェクトの影に隠れる!はい、敵が通るから後ろから撃って!倒したらすぐ引く!」
「ほらまた正面から撃ち合ってる!その癖直して!」
めちゃくちゃアドバイスしてきました。

というのも、友人はFPS(というかオーバーウォッチ)ガチ勢だったので、
初めて見たスプラトゥーンにおいてもどういう動きが強いか理解していたのです。
やったことないのに既に俺より上手そうでした。

そんなわけで、友人から受けたアドバイスをメモ代わりにまとめときます。

敵の背後を取る

「敵の進行方向と同じ向きを向いて倒す」
「敵を巻き込むように動いて倒しながら前線を進めていく」
とも言われました。
図にすると次のような感じです。

f:id:ikngtty:20170820235012p:plain

特にB帯レベルで言えば、敵も味方もエリアやヤグラやホコに向かってガーッとまっすぐ向かって行きがちです。
まさに図の赤い矢印の動きですね。
(自分もそうでした。)
この動きを冷静に読んで、自分は青い矢印の動きをするよう心がけると、おもしろいように刺さります。

例えば海女美ガチエリアで言うと、左の段差下を辿って敵陣スロープ下あたりに潜伏するのが強いみたいです。
敵が中央に向けてやんややんや突撃するのを横から見守った後、後ろから追いかけることで敵の背後が取れます。
このルートは遮蔽物も多いので、危なくなっても上手く遮蔽物を使いながら切り抜けられるっぽいです。
(後述の「オブジェクトの影に隠れる」「退路を確保する」も参照。)

敵の斜から撃つ

f:id:ikngtty:20170821000921p:plain

敵と正面から撃ち合うと、対等なエイム勝負になります。
それだとなかなか安定したキルには繋がりません。
常にこちらが優位な形のエイム勝負に持ち込むのが、安定したキルを取る立ち回りみたいです。

図のように斜から撃てば、
敵は自分にエイムを合わせるまでにワンテンポ遅れる状態で、
自分だけエイムがほぼほぼ合った状態から撃ち始めることができます。
これが自分優位な形のエイム勝負です。

斜から撃つのは常に意識する、というか、癖にしておかないとダメですね。
私の場合、試し撃ちでバルーンを正面から撃ってるだけでもう友人から叱られました

なお、これを意識するようになってから、潜伏からの奇襲の成功率が大きく上がりました。
せっかく奇襲しても、正面から襲いかかると意外と迎撃されちゃうんですよね。

エリアは対角線に沿って制圧する

f:id:ikngtty:20170821003455p:plain

これはガンガゼガチエリアで言われました。
理由は上図の通り、対角線方向を向いた方が狭い視野に集中できるからです。

オブジェクトの影に隠れる

オブジェクトっていうのは、マップに生えてるモノとか高台とかのことですね。
要は遮蔽物です。
上手い人ほどこれをちゃんと利用して敵の弾を遮り、敵地の中であっても上手く安全を確保してます。

退路を確保する

「自分優位なエイム勝負に持ち込む」と前述しましたが、裏を返せば「不利なエイム勝負は全て避ける」ということになります。
そのためには、危ない場面ではすぐに引けるようにすることが大事。
なので、退路が確保できているかどうかは常に意識し、退路がなければきちんと塗って、あらかじめ退路を作っておくことが大事です。



印象に残ってるアドバイスはこんな感じです。
当たり前のことも多いかもしれませんが、どれも初心者の自分は指摘されるまで気づかなかったことです。
他の初心者の方にも役立てば幸い。

SI会社を退職した

新卒から約4年間、某中小SI会社に勤めてきたが、先月末に退職した。

次はWebサービス業界かゲーム業界に入りたいと思っているが、
その前にまずは無職をしばらく楽しむ予定である。
時間があればじっくり勉強してみたかったことがたくさんあるし、
やりたいゲームもたくさんある。

以下、なぜ辞めたか等をまとめておきたいと思う。

辞めた理由

喩えるなら私は、
サイゼリヤで働いてみたら料理の楽しさに目覚めてしまい、
 本格的なイタリアンレストランで料理人の道を目指したくなった。」
という状態なのではないかと思う。

ここで言う"料理"とは"プログラミング"のことであり、
"サイゼリヤ"はSI業界、"本格的なイタリアンレストラン"はWebサービス業界等のことだ。

別に馬鹿にしているわけではない。
もう少し具体的に説明する。

SI業界とサイゼリヤの共通点

SI業界において、正社員は上流SEになることが求められ、そして管理職になることが求められる。
初めの内はプログラマーもやらされるが、それはあくまで下流の業務を知るため。
キャリアステップの通り道としてのフェーズでしかないし、早めに卒業するべきものとされる。

正社員がやらない分、プログラミングはなるべくパートナー社員かオフショアに回す。
その方が安く済むからである。

サイゼリヤにおける料理人も、きっとこんな感じなのではないかと思う。
正社員ははじめに少しだけ厨房に入る体験をするが、それはマネージャーになるためのステップでしかない。
厨房で働くのは基本的にバイト料理人だ。

尚、私はサイゼリヤについて一般消費者以上の知識を何も持ち合わせていないので、
サイゼリヤに関しては全て憶測で喋っていることを了承頂きたい。
もしも実際のサイゼリヤにそぐわない話があれば、
サイゼリヤに似た架空の店の話だと思って欲しい。(喩えとして意味があるのか怪しくなってくるが。)

サイゼリヤで料理の修行はしない

一般的にSI業界の(プログラミングにおける)技術意識はとても低い。
普段から最新技術についての情報にアンテナを広げていて、家でもコードを書いていて、
というような社員はかなり希少というのが私の印象だ。

少なくとも前職では、GitHubのアカウントを持っていないどころの話ではなく、
GitHubって何?」という社員が先輩後輩問わず大多数のようだった。

そんな環境も、上で述べたようなサイゼリヤ的背景を考えれば不思議ではない。
「究極においしいイタリアンを作れるよう、日々精進します!」と言いながらサイゼリヤに入る人は、きっとごく少数だろうと思う。
多くの人はマーケティングとかマネジメントに着眼するのではないだろうか。
SI業界も概ね同様なのである。(業務知識とかマネジメントとか。)

前職では、「プログラムなんて結局動けばなんだって良いよね」というような声を何度か聞いた。
ある種のプロフェッショナリズムとして、それはきっと正解なのだろう。
しかしそこにはプログラミングへの思い入れだとか、職人的なこだわりだとか、
より良いものを作ろうという熱意だとかが欠けている。
極端かもしれないが、私にはまるで、
料理人が「食えればなんだっていい」と言っているかのように聞こえた。
(これはサイゼリヤとは関係ないことにしておく。失礼になりそうなので。)

やはり料理の腕を磨くなら、本格的なイタリアンレストランで働き、同じように腕を磨き続けるレベルの高い料理人に囲まれるべきだ。

Webサービス業界はなぜ"本格的なイタリアンレストラン"なのか

Webサービス業界はSI業界と真逆で、エンジニアを続けるキャリアステップが普通に存在するし、
日々勉強しているプログラマーが非常に多い。

これはネットでもたくさん書かれていることであるし、
転職エージェントからも聞いた話なのでかなり確度の高い事実だと思われる。

このように違いが現れる理由はきっと色々あるのだろうが、
1つ挙げるならば、
「SI業界はプロジェクトの成功を考える(短期的目線)。Webサービス業界はプロダクトの成功を考える(長期的目線)。」
という要素が大きいのではないかと私は思う。

長期的にメンテナンスするプロダクトを作る

メンテナンス性の高いプログラムを書けるプログラマーを集めたほうが良い

技術力の高いプログラマーが集まる、技術力を高め続けるキャリアステップが必要になる

という理屈だ。

前職への思い

ここまで、SIを否定するような意見を述べたように見えるかもしれないが、そういうつもりはない。
世の中にはサイゼリヤも本格的イタリアンも両方必要だ。
どっちが良くてどっちが悪いというわけではない。

また、「プログラマーとしての腕を磨きたい」という観点で私はWebサービス業界等を志向するようになったが、
逆に「SEとしてやっていきたい」という場合はSI業界を志向し続けていただろう。
SI業界の技術力が低いのは、あくまでプログラミングレベルの話だ。SEのコアスキルはそこではない。

実際、私は初めの内は漠然と上流SEへの道を進むつもりでいた。
私は文系学部卒であり、学生時代のプログラミング経験はほとんど無い。
入社して実際にプログラミングに触れるまで、
自分がここまでプログラマー志向になるとは思っていなかった。

前職の方々は、そんな私の志向の変化を敏感に察知してくれ、
なるべく私がプログラミング寄りの作業をできるよう取り計らってくれたし、
将来的にも「技術長」というような感じの、例外的なキャリアパスを用意してくれているようだった。

この点について私は本当に感謝しているし、
期待に添えず退職したことは今でも申し訳なく思っている。

それでも退職したのは、私の希望があくまで「プログラマーの技術レベルが高い環境」だったからだ。
私自身が(プログラミングレベルに関して)周りを引っ張っていくということは何度も考えたが、
社員のプログラミングへの意欲が足りない限りは限界があると思ったし、
何よりメインプログラマーが取っ替え引っ替えのパートナー社員な体制では、
技術教育による技術レベルの底上げというのは不可能であるように感じられた。
それにやはり、まだまだ若い自分に必要なのは、人を引っ張ることより周りから吸収することだと思う。

尚、前職には他にも感謝することがたくさんあるが、
ここではあくまで「前職は悪いところじゃなかったよ」ってフォローがしたかっただけなので、
残りは省略する。

落ち穂拾い

ここまでがメインの辞めた理由であるが、
次職に期待することは他にも色々ある。

  • BtoCで働いて、自分が作ったものの価値を自分で体感できる方が作り甲斐がありそう。
  • 私服を始めとして、ギーク企業のラフな雰囲気が性に合ってそう。

などなど。

私は今まで「どう作るか」に非常に興味があり、「何を作るか」にはあまり注意を払ってこなかったのだが、
業界の特色上、これからは作るもの自体にも興味を持っていこうかと思っている。

補足

SI業界、Webサービス業界などと一括りに扱ったが、
これはあくまで全体的な傾向の話であるし、
例外的な会社も存在しているのは承知している。

なので、例外的に技術意識の高いSI会社に入るという選択肢も考えたが、
上記の落ち穂拾いで述べた理由も意外と大きく、
やはり業界をガラッと変えてみたいと思っている。

まとめ

技術力の高い会社に入りたいニャン。

SI業界がプログラミングを軽視する理由

前回の記事と被るところは多いです。
ikngtty.hatenablog.com

SI業界はプログラミングを軽視している

SI業界にとって、プログラミングは"卒業"するものです。

入社して最初の2~3年ほどプログラミングを経験したら、内部設計を行うようになり、外部設計を行うようになり、要件定義を行うようになり…。
だんだん上流工程に携わるようになり、代わりにプログラミングは新人やオフショアパートナーに任せるようになっていきます。

「もうfor文の書き方も忘れちゃったよw」などと笑いながらプログラマー時代を懐かしむ。
それがSI業界のキャリアパスの先にあるものです。

一方、Web業界は全く逆のようで、いくつになってもコードを書ける力はかなり重要視されると聞いています。

SI業界からWeb業界に転職を試みる人は多いようですが、あまり年齢が行っている人だと
「設計書ばっか書いてきて、結局ずっとコード書いてないわけでしょ?そんな人ウチには要らないよ。」
と門前払いを食らうらしいです。

誇り高き上流工程の経験を聞いてもらえず、新人しかやらないようなプログラミングの腕ばかり見られる。
価値観が本当に真逆ですね。

システムを作る仕事という意味では両者は同じです。なのにどうしてこんな差が生まれるんでしょうか。
ちょっとそこについて考えてみます。

プログラミングは単純作業か否か

もう少し言えば、SI業界はプログラミングを単純作業と考えているところがあります。
設計書さえあれば、あとはそれを動くものに変換するだけという感じ。

だから2~3年学べばそれ以上やっても大してスキルは変わらない。
だから10年、20年とプログラマーを続けても賃金は増えない。
だから早く卒業しなきゃいけない。

一方でWeb業界はそうした熟練プログラマーを欲しがるので、当然逆の価値観を持っています。

プログラミングは2~3年で極められるほど単純なものではない。
やればやるほど奥が深いし、その腕を磨き続けた人では生産性の次元が違う。
だから何十年も腕を磨き続ける価値があるし、磨けば磨くほど賃金を出してもらえる。

プログラミングは果たして単純作業なのでしょうか。そうではないのでしょうか。
私は、見方を変えればどちらも正解になると思っています。

プログラマーの成長曲線

プログラミングの腕を何十年も磨いていくと、人はどう成長するのでしょうか。
私は下記のようなイメージを持っています。

f:id:ikngtty:20170107141931p:plain

動くものを速く正確に作る力は、5年、10年で頭打ちになっていきます。
一方で、保守性の高いものを作る力はいつまでも伸びていきます。

つまり、単純なものを作る作業ならば単純作業ですし、工夫した良いものを作ろうとする作業ならば、頭を使う作業で、腕が要る作業だということです。

プログラマーを青い曲線で評価するか、赤い曲線で評価するか。
それはプログラムに保守性を求めるかどうかにかかってきます。

保守性について

詳しい定義は調べていないですが、私は「プログラムの変更・追加にかかるコストがいかに低く収まるようにできているか」だと思っています。

これには「一箇所を変更・追加した時の影響範囲が小さいかどうか」も含まれますし、「プログラムが読みやすいかどうか」も含まれます。
(一応後者について説明しておくと、プログラムを弄る際にはそのプログラムを読んで理解する時間が発生します。
この時間が占める割合は結構大きいです。
なのでこの時間が短く収まるプログラムほど「保守性が高い」と言えます。)

いずれにせよ、保守性を高める目的は「後で楽をすること」です。
短期的には時間・労力をかけてでも、長期的にかかる時間・労力を小さくするのが保守性を重視したプログラミングです。

SI業界は短期最適に走りやすい

Web業界はプロダクトで収益を生むので、プロダクト中心で考えて行動します。
普通、プロダクトは長期的に運用していきます。
なのでプロダクトの保守性を重視するのは当然と言えるでしょう。

一方でSI業界の収益源は何でしょうか。
作るシステムは全て他社のものです。今自分たちの作っているシステムは、この先別の会社が保守していくかもしれません。
そういった意味で、大事なのは目の前の一つ一つのプロジェクトのみです。
長期的に運用するプロダクトではなく、SI業界は短期的なプロジェクトでものを考えます。
その結果、より短期最適な戦略として、保守性は捨てた方が賢明という話になってきます。

プロダクト中心か、プロジェクト中心か。
冒頭で挙げた差はこの差から始まっているのだと私は考えました。

SI業界がプログラミングを軽視する理由

SI業界は、プロジェクトの短期的な成功を重視して保守性を捨てます。
その結果、プログラミングを単純作業とみなし、プログラマーを青い曲線で評価するようになります。
なので5年勤めた社員に払える給料はもう頭打ち。
社員にはプログラミングを"卒業"してもらうことで、別の方向での生産性の向上を図ってもらいます。

年功序列的賃金体系のもとでは、高年齢のプログラマはコストが高すぎると考える企業がある(特にプログラミングを単純作業と考える企業に多い)。俗にIT土方とも呼ばれデスマーチとなった場合は徹夜が続いたり体力が必要となってくる。そのため、プログラマとしての限界は30~35歳前後であるという説が存在した。これは「プログラマ35(30)歳定年説」と呼ばれる。

プログラマ - Wikipedia

結果、プログラミングを行うのは5年未満の新人か、単純作業が得意なオフショアパートナー。
いずれも安い賃金で済むので、そのへんも"短期的には"最適戦略というわけです。

これがプログラミング軽視の文化ができあがっていく背景だと私は考えています。

SI業界のコードが洗練されない経済学的理由

ペアプログラミングや勉強会など、洗練された綺麗なコードを目指すための取り組みはWeb系業界から多く聞くように感じます。
一方でSI業界はマネジメント関係への取り組みが多く、コーディング技術の向上にはあまり興味が無いようにも感じます。

SI業界はコードを洗練させる必要がないのでしょうか。

最近そういったことをよく考えるのですが、その中で以下の考えが芽生えてきました。

多くのSIにおいては、開発と保守が分かれている。
それゆえコードの品質が"外部化"され、"外部不経済"に陥る。

これについてまとめてみます。

"外部不経済"とは何か

例としてよく「公害」のケースが挙げられます。

例えば、工場の汚水排出問題を考えましょう。
工場が取れる選択肢は以下の2つであるとします。

1. 何も考えず汚水を垂れ流す
この場合、周辺住民が健康を害し、全部で1億円分の経済学的損失に繋がるとします。
「周辺住民の不幸分を金額で表すと1億円ぐらい」って感じですね。
「損害賠償として1億円貰わないと納得しない」と考えてもいいと思います。

2. 汚水浄化マシーンを買う
これで無事公害を防げます。
その代わり汚水浄化マシーンは1000万円します。

周辺に工場関係者しか住んでいなかったとしたら

つまり、公害の被害を全て工場が食らうとしたらです。

この場合、工場の損失は
1. (浄化しない場合)1億円
2. (浄化する場合) 1000万円
です。

当然工場は2を選びますよね。

周辺に工場関係者が住んでなかったとしたら

経済学ではこの場合、「公害のコストが外部化されている」と表現します。
つまり、公害の損害なんて工場にとっては関係ないということですね。
兵藤会長の言葉を借りれば、「彼は痛いが…わしは痛まない…!」です。

なので、工場の損失は
1. (浄化しない場合)0円
2. (浄化する場合) 1000万円
と考えます。

何も罰がないのなら、工場は1を選ぶというものです。
(実際には「信用を失う」などの損失もありそうですが。)

問題点

さて、ここまでの問題を国の立場で考え直してみましょう。
国は基本的に、「皆の幸せ値の合計が一番高ければいいな(不幸値の合計が一番低ければいいな)」と思っています。

皆の不幸値の合計はこうなります。
1. (浄化しない場合)地域住民 1億円 + 工場  0円 = 1億円
2. (浄化する場合) 地域住民  0円 + 工場 1000万円 = 1000万円

つまり2の方が望ましいわけです。
しかし、先ほどの後者のケースでは1になってしまいました。
この状況が「不経済」です。
つまり、皆の不幸値がより高くなってしまった状況です。

不経済の原因は「コストが外部化されている」ことでした。
なのでこの状況を「外部不経済」と呼ぶわけです。

解決法

実際には外部に損害をもたらした人には罰が与えられます。
国は「汚水を流したら1億円罰金!」と規制をかけておけばいいのです。
そうすれば、工場の損失は
1. (浄化しない場合)1億円
2. (浄化する場合) 1000万円
となり、工場は2を選びます。

これを「公害のコストを内部化した」と表現します。
1億円の損失を、罰金という形で工場も計算しなければいけなくなったということですね。

SI業界の"外部不経済"

前置きが長くなりましたが、本題です。

簡単に言ってしまえば、「コードの洗練」が先程の例で言う「汚水浄化」にあたると思っています。
「開発」が「工場」、「保守」が「周辺住民」です。

具体例で行ってみましょう。
SI会社は以下の内どちらかの選択肢を選べるものとします。

1. 汚いコードでさっさと納品する
初期開発費用として1000万円の人件費がかかります。
年に1回の機能追加/改修の際には毎回500万円かかります。

2. 綺麗なコードで丁寧に作り込んで納品する
初期開発費用として1500万円の人件費がかかります。
年に1回の機能追加/改修の際には毎回200万円かかります。

システムを5年は使い続けるものだとします。

前提として、汚いコードでもテストはちゃんとやっており、バグは直してるものとして下さい。
あくまでもコードの綺麗さはメンテナンス性のために目指すものとしています。

開発と保守をセットで行える場合

1. (汚いコード) 5年で総費用3500万円
2. (綺麗なコード)5年で総費用2500万円

当然2を選びます。

保守は他がやってくれる場合

1. (汚いコード) 初期開発費用1000万
2. (綺麗なコード)初期開発費用1500万

これは1を選びますよね。

企業は基本的に自分のビジネスが最優先です。
保守が苦しむことになろうと、自分たちに関係が無いのならばお金を出して助けようとは思いません。
結果としてこれは、周辺住民と工場の関係と同じになります。


実際には、開発が保守を完全にやらないというケースは稀だと思います。
しかし、「次の機能追加を自分たちがやるとは限らない」という雰囲気が強まるほど、この問題は立ち現れるでしょう。

「このシステムは自分たちが長く作っていくものだ」という認識がなければ、綺麗なコードを目指そうという動機が生まれない。
ここがWeb系業界との差だと考えています。

エンドユーザーの視点

エンドユーザーとしては、トータルで安く買いたいはずです。
しかし今回の例のように「次の機能追加が200万円で済む」などと事前に分かることは、実際はほとんど無さそうです。

そうするとエンドユーザーができることは結局、初期開発の見積が一番安いところを選ぶことだけではないでしょうか。
こうなると勝つのは汚いコードを書く会社ですよね。
これもSI業界が綺麗なコードをなかなか目指すことが出来ない理由だと思います。

そもそもの問題は、エンドユーザーがコードの汚さを測れないところにあります。
公害の例で言えば、国は川の汚染状況を測ることができるため、対策を打つことができました。
エンドユーザーはこれができません。
コードの海は知らない内にどんどん汚染されていくのです。
保守費用がいくら膨れ上がっても、「機能追加ってやけにお金がかかるものなんだなぁ」と感じるだけではないでしょうか。

解決法1

エンドユーザーが打てる手としては、まずはSI会社にとっての保守費用を内部化させることです。
つまり、「このシステムはずっと御社に任せたい。」と表明するような体制です。

1エンジニアとして思うのですが、自分たちで作ったシステムを他社にメンテナンスされたり、他社が作ったシステムを自分たちで弄ったりすることは苦労が多いです。
どこの会社が作ったかも分からない汚染物質だらけのコードの海を任されて、一から洗浄する時間も与えられず、せめて海からなるべく早く帰ってくることを目指すしかない。
そのためには、自分たちも海を汚していくしかない。
というかこんだけ汚れてたら今更どれだけ汚しても変わらないよね……。
こうしてエンジニアとしての清らかな精神も、汚染物質にすっかりやられてしまいます。

こんな悲劇を避けて、お互い幸せになりたいところです。

しかし、これはエンドユーザーとSI会社の間に深い信頼がないとできないかもしれませんね。
初期開発に時間をかけるのは後の保守効率のためであること。そのためトータルでは安く済むこと。
このへんを話し合って認識を共有しておく必要があります。
また、この体制では保守時の見積額に競争原理が働かなくなってしまいます。
つまりSI会社は保守金額を無駄に釣り上げることもできるということです。
そこに関しても信頼関係や、事前の深い話し合いが必要かと思います。

解決法2

もう一つの作戦として、エンドユーザーがコードの汚さを測ればいいんじゃないかと私は考えています。
実際にはエンドユーザー自身が測るのではなく、第三者によるコード品質監査サービスを利用する形で考えています。
(そういうサービス、あるんでしょうかね?
 もしも無いとしたら、これはビジネスチャンスなんじゃないかとも思います。)

コードの汚さが数値化できれば話は早いです。
コードの汚さが一定以下であることを要件に入れてもいいですし、コードの汚さに応じて払う金額が変わるような契約を結んでもいいんじゃないかと思います。
いずれにせよ、これでSI会社側には時間とお金をかけてでもコードを洗練させる理由が生まれます。

まとめ

というわけで、「SI業界はコードを洗練させる必要がないのか」という問いに対しては、「現状ではそうなってしまう」というのが私の考えです。
もちろんバグの発生を抑えるようなコードの洗練は必要だと思いますが、一定レベル以上の洗練はメンテナンス性の向上が主目的だと思っているので、そこから必要なくなってくると考えています。

エンドユーザーからの打開策も考えてみましたが、いずれも「エンドユーザーがコードの汚さを後の損害(いわゆる"技術的負債")と認識できること」が前提です。
エンドユーザーとSIが今よりもっと深く話し合わないと難しそうな気がします。

何より、今のSI業界はコードを洗練させない方向でそのまま戦略を最適化しようとしてるように思えます。
綺麗なコードを書ける高い人より、汚いコードしか書けない安い人を使う。
優秀な人は下流ではなく上流で使う。
オフショア開発の流れなどはその典型的な表れですね。


ここまで自分の考えをまとめてみましたが、推測が多分に含まれています(特にエンドユーザーとSI会社がどういった契約の結び方をしているのかとか)。
何か認識に誤りがあればコメントで指摘して頂けると幸いです。

おまけ:レモン市場

今回は「外部不経済」というキーワードで分析してみましたが、「質の悪いものを納品されてもエンドユーザーは分からない」という点で言えば、経済学で言う"レモン市場"の状況にも当てはまりそうです。

wikipedia:レモン市場

前述の解決法2はこれへの対処法とも言えそうです。

では、このへんで。