Reputation: 87
Here is the scenario, I was asked the following question at an interview and I managed to solve part of it, but I had some trouble with the second part of the question (and I don't even know if I solved the first part correctly, I just came up with what I could code best under the circumstances). So let me introduce the problem:
Consider the following 2-player game played on a string of dashes and plusses. If, at the beginning of your turn, the string doesn't contain a pair of adjacent dashes, you win! Otherwise, choose any pair of adjacent dashes “--
” in the string and replace it with “++
”. We take turns until someone wins.
Example game: -+-----+
--> -+-++--+
--> -+-+++++
(game is over).
Write a function listMoves()
which takes a position string as an argument and returns a list of all the valid moves.
Examples:
listMoves("") == []
listMoves("--+---+") == ["+++---+", "--+++-+", "--+-+++"]
My solution to this (in JavaScript) was:
var listMoves = function(arg) {
if (arg === null) return;
if (arg === "") {
return [];
}
var result = [];
var temp = '';
var string = [];
for (var i=0; i<arg.length; i++) {
if (temp == '-' && arg[i] == '-') {
string = arg.split('');
string[i-1] = '+';
string[i] = '+';
console.log(string);
result.push(string);
} else if (arg[i] == '-') {
temp = arg[i];
} else if (arg[i] == '+' && temp == '-') {
temp = '';
}
}
return result;
}
The second part of the question was:
In best play, each position is either a win or a loss for the player to move. Write a function isWin(position)
that returns true just when the position is a win for the player to move.
Examples:
isWin("") == true
isWin("---+") == false
isWin("----") == true
isWin("--+----+--+---++--") == ???
I managed to fathom that I needed a recursive algorithm for this, and that I could use the function I created for question #1 (hence why I included it).
I couldn't, however, put my thoughts into code.
For future reference, could someone show me how they would go about solving a problem like this?
edit#1 (added my attempt during the interview):
var isWin = function (position) {
if (position === null) return;
if (position === "") return true;
var possibleMoves = listMoves(position);
var win;
if (possibleMoves.length < 1) {
win = true;
} else if (possibleMoves.length == 1) {
win = false;
} else {
for (move in possibleMoves) {
isWin(move);
}
}
return win;
}
Upvotes: 2
Views: 197
Reputation: 39287
By recursively calling listMoves
on the results, you can obtain a tree of all possible outcomes.
For example ------
:
All terminal nodes are possible end states for the game. However we are trying to figure out from any starting position, when players play optimally, if that state is a winning state.
This can be determined by checking if there exists a chain of moves that results in the other playing being forced to choose a move that leads to the starting player to be given a win condition state:
So for our previous example, starting player has 5 choices:
Choice 1 would give the next player 3 choices:
Choice 2 would give the next player 2 choices. Both of the next player's choices would result in the starting player winning when it becomes their turn again.
Choice 3 would give the next player 2 choices. Both of the next player's choices result in the starting player getting their turn with a single choice. That single choice results in the next player would winning when they get their turn back.
Same as choice 2.
Same as choice 1.
Because choice 2 and 4 exist, the starting state is a win for the starting player.
minimax
is a recursive function that uses that logic to find if the starting position is a win or loss for the starting player.
For our problem we can label the starting player, player1
, as True
. The other player, player2
, as False
.
If minimax
is called for some state, s
, and that state has no possible moves. Then minimax
will return the player it was called with.
When minimax
is called for s
and player1
, player == True
, where there are possible moves, it returns if there are any moves that result in minimax(move, player2)
that return player1
. (If there are any player1
results, the player would choose that result).
When minimax
is called for s
and player2
, player == False
, where there are possible moves, it returns if all results of minimax(move, player1)
returns player1
. (If there no results that return player2
, player2
must choose a move that results in player1
, otherwise player2
would choose a move that results player2
winning).
Javascript:
function listMoves(s) {
var moves = [];
for (var i = 0; i < s.length - 1; i++) {
if (s.substring(i, i + 2) === '--') {
moves.push(s.substring(0, i) + '++' + s.substring(i + 2));
}
}
return moves;
}
function minimax(s, player) {
var moves = listMoves(s);
if (moves.length === 0) {
return player;
}
if (player) {
return moves.some(function(move) {
return minimax(move, !player)
});
} else {
return moves.every(function(move) {
return minimax(move, !player);
});
}
}
function isWin(s) {
return minimax(s, true);
}
document.write("<pre>" + isWin("--+----+--+---++--"), '"--+----+--+---++--"' + "</pre>");
// From http://stackoverflow.com/a/12628791/635411
function cartesianProductOf() {
return _.reduce(arguments, function(a, b) {
return _.flatten(_.map(a, function(x) {
return _.map(b, function(y) {
return x.concat([y]);
});
}), true);
}, [[]]);
};
var res = {}
for (var i = 1; i <= 6; i++) {
var s = Array.apply(null, new Array(i)).map(String.prototype.valueOf, "-+");
res[i] = {};
cartesianProductOf.apply(null, s).forEach(function(state) {
res[i][state] = isWin(state);
});
}
document.write("<pre>" + JSON.stringify(res, null, 4) + "</pre>");
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.8.0/lodash.min.js"></script>
Python:
def list_moves(s):
moves = []
for i in range(len(s) - 1):
if s[i] == "-" and s[i + 1] == "-":
moves.append(s[:i] + "++" + s[i + 2:])
return moves
def minimax(s, player=True):
moves = list_moves(s)
if not moves:
return player
n = (minimax(move, not player) for move in moves)
return any(n) if player else all(n)
def is_win(s):
return minimax(s)
print is_win(""), '""'
print
print is_win("-"), '"-"'
print is_win("+"), '"+"'
print
print is_win("--"), '"--"'
print is_win("+-"), '"+-"'
print is_win("-+"), '"-+"'
print is_win("++"), '"++"'
print
print is_win("----"), '"----"'
print is_win("+---"), '"+---"'
print is_win("-+--"), '"-+--"'
print is_win("--+-"), '"--+-"'
print is_win("---+"), '"---+"'
print is_win("++--"), '"++--"'
print is_win("-++-"), '"-++-"'
print is_win("--++"), '"--++"'
print is_win("-+-+"), '"-+-+"'
print is_win("+-+-"), '"+-+-"'
print is_win("+--+"), '"+--+"'
print is_win("+++-"), '"+++-"'
print is_win("-+++"), '"-+++"'
print is_win("++++"), '"++++"'
print
print is_win("-----"), '"-----"'
print is_win("------"), '"------"'
print is_win("-------"), '"-------"'
print
print is_win("--+----+--+---++--"), '"--+----+--+---++--"'
<pre>
True ""
True "-"
True "+"
False "--"
True "+-"
True "-+"
True "++"
True "----"
False "+---"
False "-+--"
False "--+-"
False "---+"
False "++--"
True "-++-"
False "--++"
True "-+-+"
True "+-+-"
False "+--+"
True "+++-"
True "-+++"
True "++++"
True "-----"
True "------"
False "-------"
True "--+----+--+---++--"
</pre>
Upvotes: 4