第13回オフラインリアルタイムどう書くをJavaで

[積み木の水槽 〜 横へな 2013.9.6](http://nabetani.sakura.ne.jp/hena/ord13blocktup/)

めっちゃ時間かかった。自分才能なさすぎ。コード量多すぎ。

ロジックは以下。

1. インプットの情報で積み木の水槽を作る
1. とりあえず全部水で満たす
1. 右左下のいずれかに移動できる水を削除

3番目のステップを、削除できる水が無くなるまで繰り返す。

#実装
##起動用クラス
```java:BlockTank.java
package hoge;

import java.util.ArrayList;
import java.util.List;

public class BlockTank {
    
    public static void main(String[] args) {
        new BlockTank().execute(args[0]);
    }

    public void execute(String arg) {
        // 引数解析
        List<Integer> design = new ArrayList<>();
        for (int i=0; i<arg.length(); i++) {
            design.add(Integer.valueOf(arg.substring(i, i+1)));
        }
        
        Field field = new Field(design);
        field.spillWater();
        int amount = field.getAmountOfWater();
        
        // 結果表示
        System.out.println(field);
        System.out.println(amount);
    }
}
```

##積み木を置くフィールド
```java:Field.java
package hoge;

import static hoge.Cell.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Field {

    private int LEFT_EDGE = 0;
    private int RIGHT_EDGE;
    private int BOTTOM_EDGE = 0;
    
    private Cell[][] field;
    private int width;
    private int height;
    
    public Field(List<Integer> design) {
        this.initMatrix(design);
        this.fillWater();
        this.putBlocks(design);
    }
    
    private void initMatrix(List<Integer> design) {
        width = design.size();
        height = Collections.max(design);
        
        field = new Cell[width][height];
        
        RIGHT_EDGE = width - 1;
    }
    
    private void fillWater() {
        for (Cell[] column : field) {
            Arrays.fill(column, WATER);
        }
    }
    
    private void putBlocks(List<Integer> design) {
        for (int x=0; x<design.size(); x++) {
            int wallHeight = design.get(x);
            
            for (int y=0; y<wallHeight; y++) {
                field[x][y] = BLOCK;
            }
        }
    }
    
    public void spillWater() {
        boolean remove;
        
        do {
            remove = forEachFieldCell(new CallBack<Boolean>() {
                private boolean remove;
                
                @Override public void visit(int x, int y, Cell cell) {
                    if (cell == WATER) {
                        if (isEdge(x, y) || existsBlankAround(x, y)) {
                            field[x][y] = BLANK;
                            remove = true;
                        }
                    }
                }
                
                @Override public Boolean result() {
                    return remove;
                }
            });
        } while(remove);
    }
    
    private boolean existsBlankAround(int x, int y) {
        Cell left   = field[x-1][y];
        Cell right  = field[x+1][y];
        Cell bottom = field[x][y-1];
        
        return left == BLANK || right == BLANK || bottom == BLANK;
    }

    private boolean isEdge(int x, int y) {
        return x == LEFT_EDGE || x == RIGHT_EDGE || y == BOTTOM_EDGE;
    }
    
    public int getAmountOfWater() {
        return forEachFieldCell(new CallBack<Integer>() {
            private int amount = 0;
            
            @Override public void visit(int x, int y, Cell cell) {
                if (cell == WATER) {
                    amount++;
                }
            }
            
            @Override public Integer result() {
                return amount;
            }
        });
    }

    private <R> R forEachFieldCell(CallBack<R> callback) {
        for (int x=0; x<width; x++) {
            for (int y=0; y<height; y++) {
                callback.visit(x, y, field[x][y]);
            }
        }
        
        return callback.result();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int y=height-1; 0<=y; y--) {
            for (int x=0; x<width; x++) {
                sb.append(field[x][y]);
            }
            if (y != 0) {
                sb.append(System.getProperty("line.separator"));
            }
        }
        return sb.toString();
    }
    
    private static interface CallBack<R> {
        void visit(int x, int y, Cell cell);
        R result();
    }
}
```

##フィールドに置けるもの
```java:Cell.java
package hoge;

public enum Cell {
    BLANK("　"),
    BLOCK("■"),
    WATER("水"),
    ;
    
    private String value;
    private Cell(String value) {
        this.value = value;
    }
    @Override
    public String toString() {
        return this.value;
    }
}
```

##JUnit
```java:BlockTankTest.java
package hoge;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import hoge.BlockTank;
import java.io.PrintStream;
import org.junit.Before;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

@RunWith(Theories.class)
public class BlockTankTest {
    
    @DataPoints
    public static Fixture[] fixtures = {
        new Fixture() {{
            input = "83141310145169154671122";
            expected = 24;
        }},
        new Fixture() {{
            input = "923111128";
            expected = 45;
        }},
        new Fixture() {{
            input = "923101128";
            expected = 1;
        }},
        new Fixture() {{
            input = "903111128";
            expected = 9;
        }},
        new Fixture() {{
            input = "3";
            expected = 0;
        }},
        new Fixture() {{
            input = "31";
            expected = 0;
        }},
        new Fixture() {{
            input = "412";
            expected = 1;
        }},
        new Fixture() {{
            input = "3124";
            expected = 3;
        }},
        new Fixture() {{
            input = "11111";
            expected = 0;
        }},
        new Fixture() {{
            input = "222111";
            expected = 0;
        }},
        new Fixture() {{
            input = "335544";
            expected = 0;
        }},
        new Fixture() {{
            input = "1223455321";
            expected = 0;
        }},
        new Fixture() {{
            input = "000";
            expected = 0;
        }},
        new Fixture() {{
            input = "000100020003121";
            expected = 1;
        }},
        new Fixture() {{
            input = "1213141516171819181716151413121";
            expected = 56;
        }},
        new Fixture() {{
            input = "712131415161718191817161514131216";
            expected = 117;
        }},
        new Fixture() {{
            input = "712131405161718191817161514031216";
            expected = 64;
        }},
        new Fixture() {{
            input = "03205301204342100";
            expected = 1;
        }},
        new Fixture() {{
            input = "0912830485711120342";
            expected = 18;
        }},
        new Fixture() {{
            input = "1113241120998943327631001";
            expected = 20;
        }},
        new Fixture() {{
            input = "7688167781598943035023813337019904732";
            expected = 41;
        }},
        new Fixture() {{
            input = "2032075902729233234129146823006063388";
            expected = 79;
        }},
        new Fixture() {{
            input = "8323636570846582397534533";
            expected = 44;
        }},
        new Fixture() {{
            input = "2142555257761672319599209190604843";
            expected = 41;
        }},
        new Fixture() {{
            input = "06424633785085474133925235";
            expected = 51;
        }},
        new Fixture() {{
            input = "503144400846933212134";
            expected = 21;
        }},
        new Fixture() {{
            input = "1204706243676306476295999864";
            expected = 21;
        }},
        new Fixture() {{
            input = "050527640248767717738306306596466224";
            expected = 29;
        }},
        new Fixture() {{
            input = "5926294098216193922825";
            expected = 65;
        }},
        new Fixture() {{
            input = "655589141599534035";
            expected = 29;
        }},
        new Fixture() {{
            input = "7411279689677738";
            expected = 34;
        }},
        new Fixture() {{
            input = "268131111165754619136819109839402";
            expected = 102;
        }},
    };
    
    @Before
    public void setup() {
        System.setOut(new PrintStream(System.out) {
            @Override
            public void println(int i) {
                actual = i;
                System.out.println("amount of water = " + i);
            }
        });
    }
    
    private int actual;
    
    @Theory
    public void test(Fixture fixture) {
        System.out.println("input = " + fixture.input);
        
        // exercise
        new BlockTank().execute(fixture.input);
        
        // verify
        assertThat(fixture.input, actual, is(fixture.expected));
        
        System.out.println();
    }
    
    private static class Fixture {
        public String input;
        public int expected;
    }
}
```

単純なコールバック関数が欲しいときに Java の限界を感じた。