最低給与でのAI:私たちはソコバンを書き、それを解決するようにコンピューターに教えます

コンピューターがソコバンを演じる



この記事では、有名なソコバンのおもちゃの独自の実装を作成する方法と、それを最初から解決するためのアルゴリズムについて説明します。同時に、いくつかのデザインパターンとSOLIDの原則を実践します。



すべてのコードはにあります



バックグラウンド



プッシュボタン式の電話を使用しています。エンターテインメントからはラジオと15レベルの唯一のゲームソコバン。これらのうち、私は10を解決しただけで、11番目に行き詰まりました。一日中電車に乗って、この不運な第11レベルを解決したが、成功しなかった。それから、このタスクに十分なプログラミング経験があるので、コンピューターを使用することにしました。私は目標を設定しました:このおもちゃの解決策を自分で実装することです。その結果、この記事が登場しました。



同じ11レベル(記事の最後にある解決策):



十一



おもちゃ自体は、ボックス、壁、タグが点在する長方形の2Dフィールドです。ボックスは押すことはできますが、引くことはできません。これが全体の難しさです。あなたの目標は、すべてのボックスをマークのあるセルに移動することです。ゲームの例:



レベルの例



実装を書きます



GUI . - Rogue-like, . . :



  • # , .
  • O .
  • X , .
  • @ .
  • . .
  • G .


— . , . MVC — , , . , , . . . — - . , SOLID. , . — , — , — . , . . . :



  • , , GUI . .
  • .


— , :



public class ConsoleController implements Controller
{
    private final Model model;
    private final View view;

    public ConsoleController(final Model model)
    {
        this.model = model;
        this.view = new ConsoleView(model);
    }
    // methods
}


model , , view . , ConsoleController View, ConsoleView, . ConsoleView ConsoleController, , :



public class ConsoleController implements Controller
{
    private final Model model;
    private final View view;

    public ConsoleController(final Model model, final View view)
    {
        this.model = model;
        this.view = view;
    }
    // methods
}


ConsoleController . D SOLID , . . , — . , - Spring , . .



. , ?





  • (row, col), row , col , . — , . , , , . .


, . , , , . "" — , , . :



アプリケーションアーキテクチャ



, , Model. Sokoban , . move() . Sokoban . getCrates() getMarks() . , : A* (A star algorithm).



run() " -> -> -> "



, :



###########
#.@..O..X.#
###########


- . " ". : CharSokobanFactory , , , . , Sokoban , , :



    public Sokoban(final Field field, final Set<Point> crates, final Set<Point> marks, final Point player)
    {
        this.field = field;
        this.crates = crates;
        this.marks = marks;
        this.player = player;
    }


CharSokobanFactory. , . .



抽象工場



vi-like :



  • j
  • k
  • h
  • l


, :



class Sokoban {
    // some stuff
    public boolean solved()
    {
        return marks.equals(crates);
    }
    // other stuff
}


if- , Direction, . Move, Direction move(direction) . Move.resolve, .

" ". : , 4 .

O SOLID, Open-Closed Principle: . , . , , . - , , .



:



方向



:



class ConsoleController {
//
    @Override
    public void run()
    {
        view.say("Sokoban Starts");
        char symbol = '0';
        view.render();
        final List<Move> history = new LinkedList<>();
        while (symbol != 'q')
        {
            final Move move = Move.resolve(symbol);
            if (move != null)
            {
                history.add(move);
                move.perform(sokoban);
                view.render();
                if (sokoban.solved())
                {
                    view.say("YOU WIN!");
                    break;
                }
            }
            try
            {
                symbol = (char) System.in.read();
            }
            catch (IOException io)
            {
                view.say("   :");
                throw new IllegalStateException(io);
            }
        }
        view.say("Your moves: " +  Move.compress(history));
    }
//
}


, , . , "" , , . .



:



class ConsoleView {
//
    @Override
    public void render()
    {
        clearConsole();
        for (int i = 0; i < sokoban.numberOfFieldRows(); i++)
        {
            for (int j = 0; j < sokoban.numberOfFieldCols(); j++)
            {
                final Point at = new Point(i, j);
                final Tile tileAt = sokoban.tile(at);
                if (tileAt == null)
                    break;
                final char symbolToPrint = tileAt == Tile.CRATE && sokoban.isMarkAt(at) ? Tile.CRATE_ON_MARK.symbol() : tileAt.symbol();
                System.out.print(symbolToPrint);
            }
            System.out.println();
        }
    }
//
}


15 . G (Good) , , . , .



. :



  • , . , .


, "walkable" Tile:



public enum Tile {
    WALL('#', false),
    GRASS('.', true),
    CRATE('O', false),
    MARK('X', true),
    CRATE_ON_MARK('G', false),
    PLAYER('@', true);

    private final char symbol;
    private final boolean walkable;

    Tile(final char symbol, final boolean walkable) {
        this.symbol = symbol;
        this.walkable = walkable;
    }

    public boolean isWalkable() {
        return walkable;
    }
}


, :



public Sokoban {
//  Sokoban
    public Tile tile(final Point at) {
        if (player.equals(at))
            return Tile.PLAYER;
        //
        if (crates.contains(at))
            return Tile.CRATE;
        //
        return field.tileAt(at);
    }
    public boolean isWalkableTileAt(final Point at) {
        return tile(at).isWalkable();
    }
//  Sokoban
}


, , , . :



public class Main {
    public static void main(String[] args) {
        final Sokoban sokoban = CharSokobanFactory.fromFile(args[0]).make();
        final View view = new ConsoleView(sokoban);
        final Controller game = new ConsoleController(sokoban, view);
        // 
        game.run();
    }
}


:



############
#.XO.......#
#.XO......@#
#.XO.......#
############


, :



決定



, , , . . , — .





? . . , , :



構成グラフ



, , . . , , , . . ,

, :



インシデントグラフ



, . , (1:1), .



. — .

. . , . , , . , — Breadth-First Search (BFS).



BFS



"" — (Depth-First Search) — BFS , . , . BFS (FIFO), DFS (LIFO), . BFS:



BFS(G, s)
1.  ,    :
    * v.p = null // 
    * v.marked = false //     
2.   Q
3. Q.enqueue(s)
4.  Q  :
    4.1 v = q.poll()
    4.2 v.marked = true
    4.3  v   ,   
    4.4     v , child:
        4.4.1   child.marked, :
            4.4.1.2 child.p = v
            4.4.1.3. q.enqueue(child)


v.p v. v.marked , v . v "" v -> v.p -> v.p.p -> ... -> s -. .



. .

. , , . . , :



デッドロック



, :



public class BFSSolver {
//
    protected List<Pair<Sokoban, List<Direction>>> deriveChildren(final Sokoban parent) {
        final Map<Point, Point> walkablePoints = shortestPathsFromPlayer(parent);
        final List<Pair<Sokoban, List<Direction>>> result = new LinkedList<>();
        for (final Point crate : parent.getCrates()) {
            final Point[][] pairs = new Point[][]{{crate.left(), crate.right()}, {crate.right(), crate.left()},
                    {crate.up(), crate.down()}, {crate.down(), crate.up()}};
            for (Point[] pair : pairs) {
                final Point playerWillStand = pair[0];
                final Point crateWillGo = pair[1];
                if (canMoveCrate(parent, playerWillStand, crateWillGo, walkablePoints) && !isDeadPosition(parent, crateWillGo)) {
                    final LinkedList<Direction> pathToChild = unwindWalk(walkablePoints, playerWillStand);
                    pathToChild.add(crate.derive(crateWillGo));
                    final Sokoban child = parent.derive(crate, crateWillGo);
                    result.add(Pair.pair(child, pathToChild));
                }
            }
        }
        return result;
    }
//
}


shortestPathsFromPlayer parent . walkablePoints v v.p. isDeadPosition . derive Sokoban "" :



    public Sokoban derive(final Point crateToRemove, final Point crateToInsert)
    {
        final Set<Point> childConfiguration = new HashSet<>(crates);
        childConfiguration.remove(crateToRemove);
        childConfiguration.add(crateToInsert);
        return new Sokoban(this.field, childConfiguration, Collections.unmodifiableSet(this.marks), crateToRemove);
    }


, "" . , Point (immutable). "", , BFS, . v.p v.marked .



:



public class BFSSolver {
//
    public List<Move> solve(final Sokoban start) {
        final Map<Sokoban, Pair<Sokoban, List<Direction>>> childToParentAndDirection = new HashMap<>();
        final Set<Sokoban> visited = new HashSet<>();
        final Queue<Sokoban> toVisit = new LinkedList<>();
        toVisit.add(start);
        boolean found = false;
        Sokoban parent = null;
        while (!toVisit.isEmpty()) {
            parent = toVisit.remove();
            if (parent.solved()) {
                found = true;
                break;
            }
            visited.add(parent);
            for (final Pair<Sokoban, List<Direction>> pair : deriveChildren(parent)) {
                final Sokoban child = pair.first;
                final List<Direction> walkFromParentToChild = pair.second;
                if (!visited.contains(child)) {
                    childToParentAndDirection.put(child, Pair.pair(parent, walkFromParentToChild));
                    toVisit.add(child);
                }
            }
        }
        return found? unwind(parent, childToParentAndDirection) : new LinkedList<>();
    }
//
}


, , BFS , . , , , . , , . ,

"" , . , . .



* (A star), . * .. h . h . — , h .

"" . . AStarSolver . , .



新しいAIアルゴリズムを開始するためにAIController、コンソールからコマンドを読み取らないが、指定されたアルゴリズムでレベルを解決し、タイマーで解決策を失う新しいコントローラーを作成しました。で1行だけ変更する必要がありますmainアーキテクチャへの投資は成果を上げています。



public class Main {
    public static void main(String[] args) {
        final Sokoban sokoban = CharSokobanFactory.fromFile(args[0]).make();
        final View view = new ConsoleView(sokoban);
        final Solver solver = new AStarSolver();
        final int sleepBetweenMovesMillis = 300;
        final Controller game = new AIController(sokoban, view, sleepBetweenMovesMillis, solver);
        // 
        game.run();
    }
}


結論



Sokobanおもちゃの独自の実装を作成し、Abstract Factory、Factory Method、およびStrategyの設計パターンを実際に適用し、SOLIDのS、O、およびDの原則を考慮し、BFSおよびA *アルゴリズムを実装しました。



コードと記事自体の両方についてコメントをいただければ幸いです。またね!



私は電報を送っています:@outofbound




All Articles