lib/ai: add the eight-queen problem as an example
authorJean Privat <jean@pryen.org>
Tue, 5 Aug 2014 02:34:49 +0000 (22:34 -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>

lib/ai/README.md
lib/ai/backtrack.nit
lib/ai/examples/queens.nit [new file with mode: 0644]

index f4d58a7..0af1ce0 100644 (file)
@@ -6,3 +6,7 @@ It offers simple a generic class to extends and
 Contents:
 
 * see `backtrack`
+
+See the `examples` subdirectory for examples:
+
+* `examples/queens.nit`
index f8c17c5..0232b4c 100644 (file)
@@ -13,6 +13,8 @@
 # This module provides a simple abstract class `BacktrackProblem[S,A]` to be specialized for a specific problem.
 #
 # The concrete class `BacktrackSolver` is used to configure, query, and run a solver for a given problem.
+#
+# For an example, see the `queens.nit` program in the `examples` subdirectory.
 module backtrack
 
 # Abstract backtrack problem of states (`S`) and actions (`A`).
diff --git a/lib/ai/examples/queens.nit b/lib/ai/examples/queens.nit
new file mode 100644 (file)
index 0000000..d25e129
--- /dev/null
@@ -0,0 +1,153 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT.  This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A
+# PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You  are  allowed  to  redistribute it and sell it, alone or is a part of
+# another product.
+
+# Example of the famous eight-queens problem solved with the `ai::backtrack` module.
+#
+# The game is to places n queens on a n*n chessboard.
+# The constraint is that two queens cannot be on the same row, column or diagonal.
+#
+# Eg. a solution to the 8-queens problem is
+# ~~~
+# +--------+
+# |Q.......|
+# |....Q...|
+# |.......Q|
+# |.....Q..|
+# |..Q.....|
+# |......Q.|
+# |.Q......|
+# |...Q....|
+# +--------+
+#
+# This program takes an integer n as argument then display all solutions for the
+# n-queens proglem (ie. on a n*n board).
+module queens
+
+import ai::backtrack
+
+# The (eight-)queens problem, modeled naively as a `BacktrackProblem`.
+#
+# The state (`S`) is a board, modeled as an array of occupied rows.
+# The integer in each row indicates the column occupied by the queen.
+# Since there can be only one queen by row, a single `Int` is
+# enough for each row, and the whole board `rows` is just an `Array[Int]`.
+# Only the occupied rows are stored, thus the length of `rows` indicates
+# the number of occupied rows, the remaining virtual rows are considered free.
+#
+# An action (`A`) is the column where to put a queen in the first free row,
+# so modeled as an Int.
+# Actions are applied until all rows are occupied by a queen.
+class QueenProblem
+       super BacktrackProblem[Array[Int], Int]
+
+       # The initial state has no queens; so, no occupied rows.
+       redef fun initial_state do return new Array[Int]
+
+       # The board size.
+       # Hint: use 8 to be traditional.
+       var size: Int
+
+       # What are the available columns for a queen in the first free row?
+       # Just look at occupied rows above and cancel the possible columns one by one.
+       redef fun actions(rows, node)
+       do
+               # No more than 8 rows
+               if rows.length >= size then return null
+
+               # Available columns. At first, all are available.
+               var columns = new Array[Bool].filled_with(true, size)
+
+               # Look at each occupied row and cancel columns
+               var i = rows.length # delta for each diagonal
+               for r in rows do
+                       columns[r] = false # no queen under `r`
+                       var d = r - i
+                       if d >= 0 then columns[d] = false # no queen on the first diagonal
+                       d = r + i
+                       if d < size then columns[d] = false # no queen on the second diagonal
+                       i -= 1
+               end
+
+               # Collect the remaining columns, those that were not cancelled.
+               var res = new Array[Int]
+               for c in [0..size[ do if columns[c] then res.add(c)
+
+               return res
+       end
+
+       # The first free row become occupied with a queen placed where indicated.
+       redef fun apply_action(rows, column)
+       do
+               rows.add column
+       end
+
+       # Just `free` the last occupied row.
+       redef fun backtrack(rows, column)
+       do
+               rows.pop
+       end
+
+       # Are all rows are occupied?
+       redef fun is_goal(rows) do return rows.length >= size
+
+       # Draw a nice board
+       fun print_state(rows: Array[Int])
+       do
+               printn "+"
+               for i in [0..size[ do printn "-"
+               print "+"
+               for r in rows do
+                       printn "|"
+                       for i in [0..r[ do printn "."
+                       printn "Q"
+                       for i in [r+1..size[ do printn "."
+                       print  "|"
+               end
+               for r in [rows.length..size[ do
+                       printn "|"
+                       for i in [0..size[ do printn "."
+                       print  "|"
+               end
+               printn "+"
+               for i in [0..size[ do printn "-"
+               print "+"
+       end
+end
+
+# just cont solutions (no not print them)
+var quiet = false
+
+# The board size
+var size = 8
+
+# Basic argument parsing
+if args.length > 0 and args.first == "-q" then
+       args.shift
+       quiet = true
+end
+if args.length > 0 then
+       size = args.first.to_i
+       if size <= 0 then
+               print "usage: queens [-q] [size]\n -q for quiet"
+               exit 1
+       end
+end
+
+print "The {size}-queens problem"
+var pb = new QueenProblem(size)
+var s = pb.solve
+var sols = 0
+while s.is_running do
+       if s.run != null then
+               if not quiet then pb.print_state(s.state)
+               sols += 1
+       end
+end
+print "Found {sols} solutions"