Given four rows of matchsticks:
|||||||
|||||
|||
|
Two players take turns removing any number of sticks from ONE single row. The one to remove the last stick loses.
In the previous post, everything for the game was provided, except for control flow wrapping all the pieces together in a playable game.
After some fiddling I managed to put it togather, so here comes the code for a complete playable game:
#light
let ValidMove state (removeAtIndex, removeNumber) =
removeAtIndex >= 0 &&
removeAtIndex < List.length state &&
removeNumber > 0 &&
removeNumber <= List.nth state removeAtIndex
let MakeMove state (removeAtIndex, removeNumber) =
if ValidMove state (removeAtIndex, removeNumber) = false then failwith (sprintf "%A is not a valid move on %A" (removeAtIndex, removeNumber) state)
List.mapi (fun i numberOfMatches -> if removeAtIndex = i then numberOfMatches - removeNumber else numberOfMatches) state
let GenerateValidMoves state =
let mutable validMoves = []
let validIndices = [0 .. List.length state - 1]
for i in validIndices do
let validNumberOfPicks = [1 .. List.nth state i]
for n in validNumberOfPicks do
validMoves <- (i, n)::validMoves
validMoves
// Checks if a leave is winning by complete search. Since it can take extreme time
// to calculate, the parameter maxDepth is used to limit how many look-aheads are
// valid to do (recursion depth).
let rec IsWinnerLeaveRec maxDepth list =
match maxDepth with
| 0 -> false
| _ ->
(list |> List.map abs |> List.sum = 1)
||
(GenerateValidMoves list
|> List.map (MakeMove list)
|> List.map (IsWinnerLeaveRec (maxDepth - 1))
|> List.fold_left (fun x acc-> x || acc) false) = false
// checks if a leave is winning
let IsWinnerLeave list = IsWinnerLeaveRec 5 list
let rec PickWinnerMove state moves =
match moves with
|[] -> failwith "No valid moves"
|[a]-> a
|a::b when (IsWinnerLeave (MakeMove state a)) -> a
|a::b when not (IsWinnerLeave (MakeMove state a)) -> PickWinnerMove state b
|_ ->failwith "unknown state"
let GenerateWinnerMove state =
let possibleMoves = GenerateValidMoves state
PickWinnerMove state possibleMoves
open System
let GetMoveFromUser state =
printfn "enter a move (row numberToRemove)>"
let input = Console.ReadLine()
let parts = input.Split([|' '|], StringSplitOptions.RemoveEmptyEntries)
let row = int parts.[0]
let numberToRemove = int parts.[1]
(row, numberToRemove)
let GetMoveFromComputer state =
let move = GenerateWinnerMove state
printfn "The computer moves %A" move
move
let StatePrinter state =
printfn "State:"
List.mapi (
fun i x ->
printf "%i: " i
for n in [1..x] do
printf "|"
printfn ""
) state
|> ignore
let rec main (activePlayerName, activePlayerInputRequest) (otherPlayerName,otherPlayerInputRequest) state=
StatePrinter state
let move = activePlayerInputRequest state
let newState = MakeMove state move
if (List.sum newState) < 2 then
if IsWinnerLeave newState then
StatePrinter newState
printfn "The game was won by %A" activePlayerName
else printfn "The game was lost by %A" activePlayerName
else
main (otherPlayerName, otherPlayerInputRequest) (activePlayerName, activePlayerInputRequest) newState
let startState = [7;5;3;1]
main ("Player", GetMoveFromUser) ("Computer", GetMoveFromComputer) startState
ignore (Console.ReadKey())
Now let's take a look at what was added. We'll jump right into main and look at the other functions later.
let rec main (activePlayerName, activePlayerInputRequest) (otherPlayerName,otherPlayerInputRequest) state=
StatePrinter state
let move = activePlayerInputRequest state
let newState = MakeMove state move
if (List.sum newState) < 2 then
if IsWinnerLeave newState then
StatePrinter newState
printfn "The game was won by %A" activePlayerName
else printfn "The game was lost by %A" activePlayerName
else
main (otherPlayerName, otherPlayerInputRequest) (activePlayerName, activePlayerInputRequest) newState
It's a function that takes two tuples and the current state of the game.
Each tuple represents a player; the name of the player, and the function to call to get a move from that player.
StatePrinter state
Just displays the state in a way resembling the original game of matches. It might as well just have said "print_any state", but how ugly is that? (it originally looket like that :)
let move = activePlayerInputRequest state
The next step asks the user for which move he/she/it wants to make on the given state. Explanation about those functions later, let's just accept that the function returns a move the player wants to make.
let newState = MakeMove state move
Nothing arcane about the state transition either. We have a state, the user wants to perform a move on it. Well, make the move and we get a new state!
if (List.sum newState) < 2 then
if IsWinnerLeave newState then
StatePrinter newState
printfn "The game was won by %A" activePlayerName
else printfn "The game was lost by %A" activePlayerName
This is to check if the user has won the game. To be honest, the game is so deterministic that even before it has started it is already decided who will win, assuming the winner knows the pattern.
But wouldn't it be boring to just say: You begin therefore you lose.? That gives no room for the "winner-to-be" to make a mistake and let the other player win.
But once we get down to only one or zero sticks left, it might be ok to reveal the winner.
So that's what the outer "if" does, it checks if there are less than two sticks left in the game.
If so it checks if the state is a winning or a losing state, and writes out whether the current player won or lost.
else
main (otherPlayerName, otherPlayerInputRequest) (activePlayerName, activePlayerInputRequest) newState
The outer else runs if there are at least 2 sticks left, that is enough sticks to still allow for mistakes.
What happens is that the main function is called again, but now switching between who is the active and passive player, and providing the new state.
I'll not bother to go throught the "StatePrinter" function unless someone asks.
So lets look at:
let GetMoveFromUser state =
printfn "enter a move (row numberToRemove)>"
let input = Console.ReadLine()
let parts = input.Split([|' '|], StringSplitOptions.RemoveEmptyEntries)
let row = int parts.[0]
let numberToRemove = int parts.[1]
(row, numberToRemove)
The function uses an ordinary Console.ReadLine() to get the user input. It assumes that one number is input for the row index to remove at, and one number for the number of sticks to remove from that row.
It then splits the string and picks out the first part as index, and the second part as number to remove.
It returns a tuple with those two numbers, which happens to be the representation of a move.
What about the parameter "state" which is never used? Well, I needed it to give this function the same signature as the one that asks the computer for a move:
let GetMoveFromComputer state =
let move = GenerateWinnerMove state
printfn "The computer moves %A" move
move
This one is simply just a wrapper for "GenerateWinnerMove", printing out the move that the computer chooses.
Because of the really crappy algorithm limited in recursion depth you should be able to beat the game 10 times out of 10!
Hmm, look where that statement got me when playing in order to be able to get a screen dump:
State:
0: |||||
1: |||
2: |
enter a move (row numberToRemove)>
0 3
State:
0: ||
1: |||
2: |
The computer moves (0, 1)
State:
0: |
1: |||
2: |
enter a move (row numberToRemove)>
1 3
State:
0: |
1:
2: |
The computer moves (2, 1)
State:
0: |
1:
2:
The game was won by "Computer"
And that with a simplified starting state!!! shame on me!
Can you do better?
Ps. The code was a tiny bit modified from the previous post, Thanks for the comment!
Inga kommentarer:
Skicka en kommentar