friendz: use ai::backtrack instead of the ad-hoc solver
authorJean Privat <jean@pryen.org>
Tue, 5 Aug 2014 02:56:50 +0000 (22:56 -0400)
committerJean Privat <jean@pryen.org>
Mon, 11 Aug 2014 17:59:37 +0000 (13:59 -0400)
Signed-off-by: Jean Privat <jean@pryen.org>

contrib/friendz/src/friendz.nit
contrib/friendz/src/solver.nit

index e745040..b8187ff 100644 (file)
@@ -1151,14 +1151,9 @@ redef class Game
        # Update all game entities.
        fun step do
                if solver != null and not solver_pause then
-                       for i in [0..solver_steps[ do
-                               if solver.step then
-                                       solver_pause = true
-                                       break
-                               end
-                       end
-                       solver.dump
-                       if solver.is_over then solver = null
+                       if solver.run_steps(solver_steps) != null then solver_pause = true
+                       print solver.to_s
+                       if not solver.is_running then solver = null
                end
                for g in entities do
                        g.update
@@ -1231,7 +1226,7 @@ redef class Game
        end
 
        # Current solver, if any
-       var solver: nullable Solver = null
+       var solver: nullable BacktrackSolver[Grid, Action] = null
 
        # Is the solver paused?
        var solver_pause = false
@@ -1247,7 +1242,7 @@ redef class Game
                        edit_grid(grid)
                else if kc == "s" then
                        if solver == null then
-                               solver = new Solver(grid)
+                               solver = (new FriendzProblem(grid)).solve
                                solver_pause = false
                        else
                                solver_pause = not solver_pause
@@ -1255,10 +1250,10 @@ redef class Game
                        #solver.step
                else if kc == "d" then
                        if solver == null then
-                               solver = new Solver(grid)
+                               solver = (new FriendzProblem(grid)).solve
                                solver_pause = true
                        else
-                               solver.step
+                               solver.run_steps(1)
                        end
                else if kc == "+" then
                        solver_steps += 100
index 5b80fd4..604ac5c 100644 (file)
@@ -9,11 +9,48 @@
 
 # Basic game solver
 module solver
+
 intrude import grid
+import ai::backtrack
+
+# The modelization of the friendzs problem.
+class FriendzProblem
+       super BacktrackProblem[Grid, Action]
+
+       redef var initial_state
+
+       redef fun actions(state, node)
+       do
+               var last_action = node.action
+               if state.won then return null
+               if last_action != null then
+                       var res = state.frees(last_action.tile)
+                       if not res.is_empty or last_action.tile.nexts != 2 then
+                               var a = new Array[Action]
+                               for t in res do a.add t.to_action(last_action.kind)
+                               return a
+                       end
+               end
+               return state.look_start
+       end
+
+       redef fun apply_action(state, action)
+       do
+               action.tile.update(action.kind)
+       end
+
+       redef fun backtrack(state, action)
+       do
+               action.tile.update(0)
+       end
 
-# A solver with a deep-first search algorithm
-# Instead of storig grids in memory, it plays by placing monsters and backtrack by removing placed monsters.
-class Solver
+       redef fun is_goal(state)
+       do
+               return state.won
+       end
+end
+
+redef class Grid
        # Return true if grid accepts kind at tile t.
        # Accepts means that
        # * the tile is playble (free and not fixed)
@@ -43,6 +80,7 @@ class Solver
        end
 
        # Return the list of accepting neighbors of t
+       # see `accepts`
        fun frees(t: Tile): Array[Tile]
        do
                var grid = t.grid
@@ -60,23 +98,13 @@ class Solver
                return res
        end
 
-       # The grid played on
-       var grid: Grid
-
-       # Open moves to play
-       # If empty, it means the next move will be backtraking
-       var tries = new Array[Tile]
-
-       # The color played for `tries`
-       var kind = 1
-
        # Search free tiles next to lonely monsters
-       # Used to initialize `tries`
-       fun look_start: Array[Tile]
+       fun look_start: Array[Action]
        do
+               var grid = self
                var start = new Array[Tile]
                var min = 1
-               kind = 0
+               var kind = 0
 
                var tile: nullable Tile = null
 
@@ -107,17 +135,22 @@ class Solver
                                min = n
                        end
                end
+               var res = new Array[Action]
+               for t in start do
+                       res.add t.to_action(kind)
+               end
                #print "-------------"
                #print grid
                #dump
                #print "START: {tile} -> {start.join(",")}"
                #print "-------------"
-               return start
+               return res
        end
 
        # compute and print some metrics about the problem
        fun size_problem
        do
+               var grid = self
                var free = 0
                for x in [0..grid.width[ do
                        for y in [0..grid.height[ do
@@ -133,98 +166,14 @@ class Solver
                print "KINDS: {ms}"
                print "SIZE: {(ms+1).to_f.pow(free.to_f)}"
        end
-
-       init(grid: Grid)
-       do
-               self.grid = grid
-               size_problem
-               tries = look_start
-       end
-
-       # Zipper in the search tree as a FIFO
-       var history = new Array[Move]
-
-       # Perform a backtrack step
-       # Remove the placed monsters and go for the other tries
-       fun backtrack: Bool
-       do
-               if history.is_empty then
-                       is_over = true
-                       return true
-               end
-               var h = history.pop
-               tries = h.tries
-               opens -= tries.length
-               h.tile.update(0)
-               kind = h.kind
-               return false
-       end
-
-       # Number of player steps
-       var steps = 0
-
-       # Number of open in the
-       # real opens is `opens+tries.length`
-       var opens = 0
-
-       fun dump
-       do
-               print "STEPS: {steps}"
-               print "OPENS: {opens+tries.length}"
-               print "DEPTH: {history.length}"
-               print "NEXTS: {tries.join(", ")}"
-       end
-
-       # Is the last step exhausted all the possibilities?
-       var is_over = false
-
-       # Compute the next step.
-       # Return tru on a wining position (`grid.won`) or when the solver `is_over`
-       fun step: Bool
-       do
-               #print "=========="
-               #print grid
-               #dump
-               #print "=========="
-               steps += 1
-               if tries.is_empty then
-                       return backtrack
-               end
-               var t = tries.pop
-               var eval = 0
-               var fail = t.update(kind)
-               if not fail then
-                       #eval = evaluate_new_tile(t)
-                       #fail = (eval == -1)
-               end
-               if not fail then
-                       opens += tries.length
-                       var h = new Move(t, tries, kind)
-                       history.push(h)
-                       if grid.won then
-                               tries = new Array[Tile]
-                               return true
-                       end
-                       if t.nexts == 2 then
-                               tries = look_start
-                       else
-                               tries = frees(t)
-                       end
-               else
-                       t.update(0)
-               end
-               return false
-       end
 end
 
-# A stored move in the `Solver::history`
-class Move
-       # The tile played
+class Action
        var tile: Tile
-
-       # The remainig alternatives to try
-       var tries: Array[Tile]
-
-       # to color for the moves
        var kind: Int
+       redef fun to_s do return "{tile}->{kind}"
+end
+
+redef class Tile
+       fun to_action(kind_to_play: Int): Action do return new Action(self, kind_to_play)
 end