After reading the Stanford slides and this slower-paced discussion of these sorts of games, it's not clear to me what computational tricks one should learn from this. If there is a pattern in the problem, it's not likely that one could write a program to find it without first solving the problem. In addition, all these problems are candidates for dynamic programming solutions. Here's a dynamic programming solution to "1143 Number Game," which was listed as one of the 3 hardest on the Stanford list.
object NumberGame {
// There are no winning moves from the empty set
private var archive = Map[TreeSet[Int], TreeSet[Int]](TreeSet[Int]() -> TreeSet())
/**
* compute and return the excluded closure of excluded,
* i.e., all the numbers that are excluded (up to max)
* given that the numbers in excluded are excluded.
*
* This is not done efficiently, and no archive is created.
*/
private def excludedClosure(excluded: Set[Int], max: Int): Set[Int] = {
val additionalExcluded =
for (
i <- excluded;
j <- excluded;
val sum = i + j;
if (sum <= max && !excluded.contains(sum))
) yield sum
if (additionalExcluded.isEmpty) excluded
else excludedClosure(excluded ++ additionalExcluded, max)
}
private def prunedNumbers(numbers: TreeSet[Int]): TreeSet[Int] = {
if (numbers.isEmpty) TreeSet()
else {
val max = numbers.max
val excluded = (2 to max).toSet.diff(numbers)
numbers -- excludedClosure(excluded, max)
}
}
private def sort(set: Set[Int]): TreeSet[Int] = TreeSet[Int]() ++ set
/**
* Given a set of numbers return the winning moves. Format the winning moves with
* move -> remaining numbers
* on a separate line for each winning move.
*
* If the input is not valid, elements are removed to make it valid.
*/
def winningMoves(numbers: Set[Int]): String = {
val sortedNumbers = sort(numbers)
val validNumbers = prunedNumbers(sortedNumbers)
if (!validNumbers.equals(sortedNumbers)) {
println("Validating: " + sortedNumbers + " - " + (sortedNumbers -- validNumbers) + " ==> " + validNumbers)
winningMoves(validNumbers)
} else {
// This is the only important line. It returns a possibly empty List of winning moves.
val winners = winningMoves1(sortedNumbers)
// The rest formats the output. For each winning move, show what's left.
val remainders = winners.map(move => prunedNumbers(sortedNumbers - move))
"\n" + (winners.zip(remainders)).map(mr => mr._1 + " -> [" + mr._2.mkString(", ") + ']').mkString("\n")
}
}
/**
* Given a set of numbers, return a set of winning moves.
*/
private def winningMoves1(numbers: TreeSet[Int]): TreeSet[Int] =
archive.getOrElse(
numbers,
{
val max = numbers.max
val excluded = TreeSet[Int]() ++ ((2 to max).toSet.diff(numbers))
val sortedWinners =
sort(
for (
n <- numbers;
val remainingNumbers = prunedNumbers(numbers - n);
if (winningMoves1(remainingNumbers)).isEmpty
) yield n)
archive += numbers -> sortedWinners
sortedWinners
})
}
Here are some sample runs from a worksheet
import NumberGame._
object numberGameWS {
winningMoves(Set(2, 3, 4, 6, 7, 9, 11, 14, 16, 17))
//> Validating: TreeSet(2, 3, 4, 6, 7, 9, 11, 14, 16, 17) - TreeSet(16, 17) ==>
//| TreeSet(2, 3, 4, 6, 7, 9, 11, 14)
//| res0: String = "
//| 7 -> [2, 3, 4, 6, 9, 11]"
winningMoves(Set(2, 3, 4, 6, 7, 9, 11, 14))
//> res1: String = "
//| 7 -> [2, 3, 4, 6, 9, 11]"
winningMoves(Set(2, 3, 4, 6, 9, 11))
//> res2: String = "
//| "
winningMoves(Set(2, 3, 4, 6, 9, 10, 11))
//> Validating: TreeSet(2, 3, 4, 6, 9, 10, 11) - TreeSet(10) ==> TreeSet(2, 3, 4
//| , 6, 9, 11)
//| res3: String = "
//| "
winningMoves(Set(2, 3, 4, 5, 7, 8, 9, 10))
//> res4: String = "
//| 5 -> [2, 3, 4, 7, 8, 9]"
winningMoves(Set(2, 3, 4, 5, 7, 8, 9))
//> res5: String = "
//| 5 -> [2, 3, 4, 7, 8, 9]
//| 7 -> [2, 3, 4, 5, 8, 9]
//| 8 -> [2, 3, 4, 5, 7, 9]"
winningMoves(Set(2, 3, 4, 7, 8, 9))
//> res6: String = "
//| "
winningMoves(Set(2, 3, 4, 5, 7, 8))
//> res7: String = "
//| 4 -> [2, 3, 5, 7]"
winningMoves(Set(2, 3, 4, 5, 7))
//> res8: String = "
//| 4 -> [2, 3, 5, 7]
//| 5 -> [2, 3, 4, 7]
//| 7 -> [2, 3, 4, 5]"
winningMoves(Set(2, 3, 5, 6, 7, 9, 11))
//> res9: String = "
//| 6 -> [2, 3, 5, 7, 9, 11]"
winningMoves(Set(2, 3, 5, 6))
//> res10: String = "
//| "
winningMoves(Set(2, 3, 6, 7))
//> res11: String = "
//| "
winningMoves(Set(2, 3, 5, 7, 12, 16, 20))
//> Validating: TreeSet(2, 3, 5, 7, 12, 16, 20) - TreeSet(12, 16, 20) ==> TreeSe
//| t(2, 3, 5, 7)
//| res12: String = "
//| "
winningMoves(Set(2, 3, 5))
//> res13: String = "
//| 5 -> [2, 3]"
winningMoves(Set(2, 3))
//> res14: String = "
//| "
winningMoves(Set(2, 3, 8))
//> Validating: TreeSet(2, 3, 8) - TreeSet(8) ==> TreeSet(2, 3)
//| res15: String = "
//| "
winningMoves(Set(2, 3, 4, 5, 6, 7))
//> res16: String = "
//| "
winningMoves(Set(2, 3, 4, 5, 6, 8))
//> res17: String = "
//| 4 -> [2, 3, 5, 6]"
winningMoves(Set(2))
//> res18: String = "
//| 2 -> []"
winningMoves(Set(3))
//> res19: String = "
//| 3 -> []"
winningMoves(Set(3, 4))
//> Validating: TreeSet(3, 4) - TreeSet(4) ==> TreeSet(3)
//| res20: String = "
//| 3 -> []"
}