How do I refactor two case value expressions into one?
I'm afraid that I'm still not clear on how to remove duplication from this logic:
match redPiece with
| Checker checker -> checker |> set (checker |> attemptJump blackChecker yIncrementValue)
| King king -> king |> set (king |> attemptJump blackChecker yIncrementValue)
This question is probably a duplicate. However, I am still struggling to refactor this type of code smell.
How do I actually implement the wrapper function that one of the posters suggested on the link I provided?
Here's the complete function:
let jumpBlack ((blackChecker:BlackChecker),(blackCheckers:BlackChecker list)) (redPiece:RedPiece) =
let yIncrementValue = -1
let minY = 0
let set position piece =
match position with
| pos when pos = piece -> position , blackCheckers
| _ -> position , blackCheckers |> remove blackChecker
match redPiece with
| Checker checker -> checker |> set (checker |> attemptJump blackChecker yIncrementValue)
| King king -> king |> set (king |> attemptJump blackChecker yIncrementValue)
The entire domain can be found here:
(* Types *)
type BlackOption = NorthEast | NorthWest
type RedOption = SouthEast | SouthWest
type KingOption =
| NorthEast
| NorthWest
| SouthEast
| SouthWest
type Position = { X:int; Y:int }
type BlackChecker = Position
type RedChecker = Position
type BlackKing = Position
type RedKing = Position
type King =
| BlackKing of BlackKing
| RedKing of RedKing
type RedPiece =
| Checker of RedChecker
| King of RedKing
type BlackPiece =
| BlackChecker of BlackChecker
| BlackKing of BlackKing
(* Private *)
let private remove item list = list |> List.filter (fun x -> x <> item)
let private setRowPosition y1 y2 y3 index =
match index with
| x when x < 4 -> { X=x; Y=y1 }
| x when x < 8 -> { X=x-4; Y=y2 }
| _ -> { X=index-8; Y=y3 }
let private set (x, y) positions (position:Position) =
match not (positions |> List.exists (fun pos -> pos = { X=x; Y=y })) with
| true -> { X=x; Y=y }
| false -> position
let private attemptJump target yDirection source =
let updateX value = { X=target.X + value
Y=target.Y + yDirection }
match source with
| position when position.Y + yDirection = target.Y &&
position.X + 1 = target.X -> updateX 1
| position when position.Y + yDirection = target.Y &&
position.X - 1 = target.X -> updateX -1
| _ -> source
let private initializeBlack () =
let setPosition index =
index |> setRowPosition 7 6 5
let blackCheckers = List.init 12 setPosition |> List.map (fun pos -> { X=pos.X; Y=pos.Y })
blackCheckers
let private initializeRed () =
let setPosition index =
index |> setRowPosition 0 1 2
let redCheckers = List.init 12 setPosition |> List.map (fun pos -> { X=pos.X; Y=pos.Y })
redCheckers
(* Exposed *)
let moveBlack direction positions (checker:BlackChecker) =
let position = checker
match direction with
| BlackOption.NorthEast -> (positions, position) ||> set ((position.X + 1), (position.Y + 1 ))
| BlackOption.NorthWest -> (positions, position) ||> set ((position.X - 1), (position.Y + 1 ))
let moveRed direction positions (checker:RedChecker) =
let position = checker
match direction with
| RedOption.SouthEast -> (positions, position) ||> set ((position.X + 1), (position.Y - 1 ))
| RedOption.SouthWest -> (positions, position) ||> set ((position.X - 1), (position.Y - 1 ))
let moveKing direction positions (king:King) =
let position = match king with
| King.BlackKing bk -> bk
| King.RedKing rk -> rk
let result = match direction with
| NorthEast -> (positions, position) ||> set ((position.X + 1), (position.Y + 1 ))
| NorthWest -> (positions, position) ||> set ((position.X - 1), (position.Y + 1 ))
| SouthEast -> (positions, position) ||> set ((position.X + 1), (position.Y - 1 ))
| SouthWest -> (positions, position) ||> set ((position.X - 1), (position.Y - 1 ))
match king with
| King.BlackKing _ -> King.BlackKing result
| King.RedKing _ -> King.RedKing result
let jumpRed ((redChecker:RedChecker), (redCheckers:RedChecker list)) (blackChecker:BlackChecker) =
let yIncrementValue = 1
let maxY = 7
let position = blackChecker |> attemptJump redChecker yIncrementValue
match position with
| pos when pos = blackChecker -> BlackChecker position , redCheckers
| pos when pos.Y = maxY -> BlackKing position , redCheckers |> remove redChecker
| _ -> BlackChecker position , redCheckers |> remove redChecker
let jumpBlack ((blackChecker:BlackChecker),(blackCheckers:BlackChecker list)) (redPiece:RedPiece) =
let yIncrementValue = -1
let minY = 0
let set position piece =
match position with
| pos when pos = piece -> position , blackCheckers
| _ -> position , blackCheckers |> remove blackChecker
match redPiece with
| Checker checker -> checker |> set (checker |> attemptJump blackChecker yIncrementValue)
| King king -> king |> set (king |> attemptJump blackChecker yIncrementValue)
I would remove the duplication like that:
match redPiece with
| Checker piece
| King piece -> piece |> set (piece |> attemptJump blackChecker yIncrementValue)
Related
I am talking about the zip operations in the context of heterogeneous lists. I am working on a lightly dependently typed language that uses them as tuples.
type T =
| S of string
| R of T list
let rec zip l =
let is_all_r_empty x = List.forall (function R [] -> true | _ -> false) x
let rec loop acc_total acc_head acc_tail x =
match x with
| S _ :: _ -> R l
| R [] :: ys ->
if List.isEmpty acc_head && is_all_r_empty ys then List.rev acc_total |> R
else R l
| R (x :: xs) :: ys -> loop acc_total (x :: acc_head) (R xs :: acc_tail) ys
| [] ->
match acc_tail with
| _ :: _ -> loop ((List.rev acc_head |> zip) :: acc_total) [] [] (List.rev acc_tail)
| _ -> List.rev acc_total |> R
loop [] [] [] l
let rec unzip l =
let transpose l =
let is_all_empty x = List.forall (function _ :: _ -> false | _ -> true) x
let rec loop acc_total acc_head acc_tail = function
| (x :: xs) :: ys -> loop acc_total (x :: acc_head) (xs :: acc_tail) ys
| [] :: ys ->
if List.isEmpty acc_head && is_all_empty ys then loop acc_total acc_head acc_tail ys
else l
| [] ->
match acc_tail with
| _ :: _ -> loop (List.rev acc_head :: acc_total) [] [] (List.rev acc_tail)
| _ -> List.rev acc_total
loop [] [] [] l
let is_all_r x = List.forall (function R _ -> true | _ -> false) x
match l with
| R x when is_all_r x -> List.map unzip x |> transpose |> List.map R
| R x -> x
| S _ -> failwith "Unzip called on S."
//let a = R [R [S "a"; S "t"]; R [S "b"; S "w"]; R [S "c"; S "e"]]
//let b = R [R [S "1"; S "4"]; R [S "5"; S "r"]; R [S "3"; S "6"]]
//let c = R [R [S "z"; S "v"]; R [S "x"; S "b"]; R [S "c"; S "2"]]
//
//let t3 = zip [a;b]
//let t4 = zip [t3;c]
//let u1 = unzip t4
//let r1 = u1 = [t3;c]
//let u2 = unzip t3
//let r2 = u2 = [a;b] // The above works fine on tuples with regular dimensions.
let a = R [R [S "q"; S "w"; S "e"]]
let b = R [R [S "a"; S "s"]; R [S "z"]; S "wqe"]
let ab = [a;b]
let t = zip ab
let t' = unzip t
ab = t' // This is false, but I would like the ziping and then unziping to be reversible if possible.
Zipping and unzipping in general can be expressed as a dimensional shift or a series of transposes. That is all these two functions are doing.
They behave well on regular tuples, but I would like zip+unzip to be isomorphic on irregular ones as well. My intuition is telling me that this would be asking too much of them though.
I need a second opinion here.
#r "../../packages/FsCheck.2.8.0/lib/net452/FsCheck.dll"
type T =
| S of string
| VV of T list
let transpose l on_fail on_succ =
let is_all_vv_empty x = List.forall (function VV [] -> true | _ -> false) x
let rec loop acc_total acc_head acc_tail = function
| VV [] :: ys ->
if List.isEmpty acc_head && is_all_vv_empty ys then
if List.isEmpty acc_total then failwith "Empty inputs in the inner dimension to transpose are invalid."
else List.rev acc_total |> on_succ
else on_fail ()
| VV (x :: xs) :: ys -> loop acc_total (x :: acc_head) (VV xs :: acc_tail) ys
| _ :: _ -> on_fail ()
| [] ->
match acc_tail with
| _ :: _ -> loop (VV (List.rev acc_head) :: acc_total) [] [] (List.rev acc_tail)
| _ -> List.rev acc_total |> on_succ
loop [] [] [] l
let rec zip l =
match l with
| _ :: _ -> transpose l (fun _ -> l) (List.map (function VV x -> zip x | x -> x)) |> VV
| _ -> failwith "Empty input to zip is invalid."
let rec unzip l =
let is_all_vv x = List.forall (function VV _ -> true | _ -> false) x
match l with
| VV x ->
match x with
| _ :: _ when is_all_vv x -> let t = List.map (unzip >> VV) x in transpose t (fun _ -> x) id
| _ :: _ -> x
| _ -> failwith "Empty inputs to unzip are invalid."
| S _ -> failwith "Unzip called on S."
open FsCheck
open System
let gen_t =
let mutable gen_t = None
let gen_s () = Gen.map S Arb.generate<string>
let gen_vv size = Gen.nonEmptyListOf (gen_t.Value size) |> Gen.map VV
gen_t <-
fun size ->
match size with
| 0 -> gen_s()
| _ when size > 0 -> Gen.oneof [gen_s (); gen_vv (size-1)]
| _ -> failwith "impossible"
|> Some
gen_t.Value
|> Gen.sized
let gen_t_list_irregular = Gen.nonEmptyListOf gen_t
let gen_t_list_regular = Gen.map2 List.replicate (Gen.choose(1,10)) gen_t
type MyGenerators =
static member Tuple() = Arb.fromGen gen_t
static member TupleList() = Arb.fromGen gen_t_list_regular
Arb.register<MyGenerators>()
let zip_and_unzip orig = zip orig |> unzip
let zip_and_unzip_eq_orig orig = zip_and_unzip orig = orig
// For regular tuples it passes with flying colors.
Check.One ({Config.Quick with EndSize = 10}, zip_and_unzip_eq_orig)
// I can't get it to be isomorphic for irregularly sized arrays as expected.
//let f x =
// let x' = zip x
// printfn "x'=%A" x'
// printfn "unzip x'=%A" (unzip x')
// printfn "zip_and_unzip_eq_orig x=%A" (zip_and_unzip_eq_orig x)
//
//f [VV [VV [S "12"; S "qwe"]; VV [S "d"]]; VV [VV [S ""; S "ug"]; VV [S ""]]]
No matter what, I try I cannot figure out how to make the pair isomorphic for irregularly sized tuples and I feel it is unlikely that anyone will tell me differently so I'll put the above attempt as an answer for now.
On the upside, based on the tests above, I am decently sure that it should be isomorphic for all regularly sizes tuples. I guess this should suffice. I've tightened the code up a little compared to the example I had in the question.
This irregular zipping and unzipping problem would make an interesting math puzzle.
I have several verbal expressions that I've packaged into one function:
open FsVerbalExpressions
open FsVerbalExpressions.VerbalExpression
open System.Text.RegularExpressions
open System
let createOrVerbExFromList (verbExList: VerbEx list) =
let orVerbEx =
verbExList
|> List.reduce (fun acc thing -> verbExOrVerbEx RegexOptions.IgnoreCase acc thing) //simpleVerbEx
orVerbEx
let k12VerbEx =
let kTo12 = ["SCHOOL"; "DIST"; "SD"; "HS"; "BD OF ED"]
kTo12
|> List.map (fun word -> VerbEx(word))
|> createOrVerbExFromList
let twoYearCollegeVerbEx =
VerbEx("2 Year College")
let universityVerbEx =
VerbEx("UNIV")
let privateSchoolVerbEx =
VerbEx("ACAD")
//Here there be dragons:
let newInst (x: string) =
match (isMatch x k12VerbEx) with
| true -> "K - 12"
| _ -> match (isMatch x twoYearCollegeVerbEx) with
| true -> "2 Year College"
| _ -> match (isMatch x universityVerbEx) with
| true -> "University"
| _ -> match (isMatch x privateSchoolVerbEx) with
| true -> "Private / Charter School"
| _ -> "Other"
I'd like to rewrite the newInst function so that it's no longer the "pyramid of doom. My question is how can I get rid of the pyramid of doom? Can I get rid of it? I have the suspicion that it will be some sort of async workflow or other computational expression, but those are all very new to me.
If you are only matching against booleans, then if ... elif is sufficient:
let newInst (x: string) =
if isMatch x k12VerbEx then
"K - 12"
elif isMatch x twoYearCollegeVerbEx then
"2 Year College"
elif isMatch x universityVerbEx then
"University"
elif isMatch x privateSchoolVerbEx then
"Private / Charter School"
else
"Other"
A more flexible possibility would be to create an active pattern:
let (|IsMatch|_|) f x =
if isMatch x f then Some () else None
let newInst (x: string) =
match x with
| IsMatch k12VerbEx -> "K - 12"
| IsMatch twoYearCollegeVerbEx -> "2 Year College"
| IsMatch universityVerbEx -> "University"
| IsMatch privateSchoolVerbEx -> "Private / Charter School"
| _ -> "Other"
When there is sequential repetition of exactly the same form of code, I prefer to use a data-driven approach instead:
let verbExStrings =
[
(k12VerbEx, "K - 12")
(twoYearCollegeVerbEx, "2 Year College")
(universityVerbEx, "University")
(privateSchoolVerbEx, "Private / Charter School")
]
let newInst x =
verbExStrings
|> List.tryPick (fun (verbEx, string) -> if isMatch x verbEx then Some string else None)
|> function Some x -> x | _ -> "Other"
An advantage of this approach is that the raw data (verbExStrings) can come in handy in other places and is not tied to your code implementation.
How can I generate a value so that it's reflected as an element of another generated value?
For example take the following code:
type Space =
| Occupied of Piece
| Available of Coordinate
// Setup
let pieceGen = Arb.generate<Piece>
let destinationGen = Arb.generate<Space>
let positionsGen = Arb.generate<Space list>
I want the positionsGen to include the values produced by the pieceGen and spaceGen.
However, I am clueless on how to do this.
To add context to my question, my positions list (aka checker board) should contain both the generated piece and the generated destination within its list.
Here's my test:
[<Property(QuietOnSuccess = true, MaxTest=10000)>]
let ``moving checker retains set count`` () =
// Setup
let pieceGen = Arb.generate<Piece>
let destinationGen = Arb.generate<Space>
let positionsGen = Arb.generate<Space list>
let statusGen = Arb.generate<Status>
// Test
Gen.map4 (fun a b c d -> a,b,c,d) pieceGen destinationGen positionsGen statusGen
|> Arb.fromGen
|> Prop.forAll
<| fun (piece , destination , positions , status) -> (positions, status)
|> move piece destination
|> getPositions
|> List.length = positions.Length
Appendix:
(* Types *)
type Black = BlackKing | BlackSoldier
type Red = RedKing | RedSoldier
type Coordinate = int * int
type Piece =
| Black of Black * Coordinate
| Red of Red * Coordinate
type Space =
| Occupied of Piece
| Available of Coordinate
type Status =
| BlacksTurn | RedsTurn
| BlackWins | RedWins
(* Private *)
let private black coordinate = Occupied (Black (BlackSoldier , coordinate))
let private red coordinate = Occupied (Red (RedSoldier , coordinate))
let private getPositions (positions:Space list, status:Status) = positions
let private yDirection = function
| Black _ -> -1
| Red _ -> 1
let private toAvailable = function
| Available pos -> true
| _ -> false
let private available positions = positions |> List.filter toAvailable
let private availableSelection = function
| Available pos -> Some pos
| Occupied _ -> None
let private availablePositions positions =
positions |> List.filter toAvailable
|> List.choose availableSelection
let private getCoordinate = function
| Available xy -> Some xy
| _ -> None
let private coordinateOf = function
| Black (checker , pos) -> pos
| Red (checker , pos) -> pos
let private optionsForSoldier piece =
let (sourceX , sourceY) = coordinateOf piece
(fun pos -> pos = ((sourceX - 1) , (sourceY + (piece |> yDirection) )) ||
pos = ((sourceX + 1) , (sourceY + (piece |> yDirection) )))
let private optionsForKing piece =
let (sourceX , sourceY) = coordinateOf piece
(fun pos -> pos = ((sourceX - 1) , (sourceY + 1 )) ||
pos = ((sourceX + 1) , (sourceY + 1 )) ||
pos = ((sourceX - 1) , (sourceY - 1 )) ||
pos = ((sourceX + 1) , (sourceY - 1 )))
let private jumpOptions (sourceX , sourceY) space =
match space with
| Occupied p -> match p with
| Red (ch,xy) -> xy = (sourceX + 1, sourceY - 1) ||
xy = (sourceX - 1, sourceY - 1)
| Black (ch,xy) -> xy = (sourceX + 1, sourceY + 1) ||
xy = (sourceX - 1, sourceY + 1)
| _ -> false
let private jumpsForSoldier piece positions =
match piece with
| Black (ch,pos) -> positions |> List.filter (jumpOptions (coordinateOf piece))
| Red (ch,pos) -> positions |> List.filter (jumpOptions (coordinateOf piece))
let private isKing piece =
match piece with
| Black (checker , _) -> match checker with
| BlackSoldier -> false
| BlackKing -> true
| Red (checker , _) -> match checker with
| RedSoldier -> false
| RedKing -> true
let private filterOut a b positions =
positions |> List.filter(fun x -> x <> a && x <> b)
let private movePiece destination positions piece =
let destinationXY =
match destination with
| Available xy -> xy
| Occupied p -> coordinateOf p
let yValueMin , yValueMax = 0 , 7
let canCrown =
let yValue = snd destinationXY
(yValue = yValueMin ||
yValue = yValueMax) &&
not (isKing piece)
match positions |> List.find (fun space -> space = Occupied piece) with
| Occupied (Black (ch, xy)) ->
let checkerType = if canCrown then BlackKing else BlackSoldier
Available(xy) :: (Occupied(Black(checkerType, destinationXY)))
:: (positions |> filterOut (Occupied (Black(ch, xy))) destination)
| Occupied (Red (ch, xy)) ->
let checkerType = if canCrown then RedKing else RedSoldier
Available(xy) :: (Occupied(Red(checkerType, destinationXY)))
:: (positions |> filterOut (Occupied (Red(ch, xy))) destination)
| _ -> positions
(* Public *)
let startGame () =
[ red (0,0); red (2,0); red (4,0); red (6,0)
red (1,1); red (3,1); red (5,1); red (7,1)
red (0,2); red (2,2); red (4,2); red (6,2)
Available (1,3); Available (3,3); Available (5,3); Available (7,3)
Available (0,4); Available (2,4); Available (4,4); Available (6,4)
black (1,5); black (3,5); black (5,5); black (7,5)
black (0,6); black (2,6); black (4,6); black (6,6)
black (1,7); black (3,7); black (5,7); black (7,7) ] , BlacksTurn
let optionsFor piece positions =
let sourceX , sourceY = coordinateOf piece
match piece |> isKing with
| false -> positions |> availablePositions
|> List.filter (optionsForSoldier piece)
| true -> positions |> availablePositions
|> List.filter (optionsForKing piece)
let move piece destination (positions,status) =
let currentStatus = match status with
| BlacksTurn -> RedsTurn
| RedsTurn -> BlacksTurn
| BlackWins -> BlackWins
| RedWins -> RedWins
let canProceed = match piece with
| Red _ -> currentStatus = RedsTurn
| Black _ -> currentStatus = BlacksTurn
if not canProceed then (positions , currentStatus)
else let options = optionsFor piece positions
let canMoveTo = (fun target -> options |> List.exists (fun xy -> xy = target))
match getCoordinate destination with
| Some target -> if canMoveTo target then
let updatedBoard = ((positions , piece) ||> movePiece destination)
(updatedBoard , currentStatus)
else (positions , currentStatus)
| None -> (positions , currentStatus)
let jump target positions source =
let canJump =
positions |> jumpsForSoldier source
|> List.exists (fun s -> match s with
| Occupied target -> true
| _ -> false)
let (|NorthEast|NorthWest|SouthEast|SouthWest|Origin|) (origin , barrier) =
let (sourceX , sourceY) = origin
let (barrierX , barrierY) = barrier
if barrierY = sourceY + 1 &&
barrierX = sourceX - 1
then SouthWest
elif barrierY = sourceY + 1 &&
barrierX = sourceX + 1
then SouthEast
elif barrierY = sourceY - 1 &&
barrierX = sourceX - 1
then NorthWest
elif barrierY = sourceY - 1 &&
barrierX = sourceX + 1
then NorthEast
else Origin
let jumpToPostion origin barrier =
let (sourceX , sourceY) = origin
let (barrierX , barrierY) = barrier
match (origin , barrier) with
| SouthWest -> (barrierX + 1, barrierY - 1)
| SouthEast -> (barrierX + 1, barrierY + 1)
| NorthWest -> (barrierX - 1, barrierY - 1)
| NorthEast -> (barrierX - 1, barrierY + 1)
| Origin -> origin
if canJump then
let destination = Available (jumpToPostion (coordinateOf source) (coordinateOf target))
let result = (positions, source) ||> movePiece destination
|> List.filter (fun s -> s <> Occupied target)
Available (coordinateOf target)::result
else positions
As explained in a previous answer, you can use the gen computation expression to express more complex generators.
In this particular example, you state that you need positionsGen to include the values produced by the pieceGen and spaceGen. You can do that like this:
[<Property(QuietOnSuccess = true, MaxTest=10000)>]
let ``moving checker retains set count`` () =
gen {
let! piece = Arb.generate<Piece>
let! destination = Arb.generate<Space>
let! otherPositions = Arb.generate<Space list>
let! positions =
Occupied piece :: destination :: otherPositions |> Gen.shuffle
let! status = Arb.generate<Status>
return piece, destination, positions |> Array.toList, status }
|> Arb.fromGen
|> Prop.forAll
// ... the rest of the test goes here...
The computation expression starts by generating a piece and a destination. Due to the use of let! within the computation expression, within that context, they are normal Piece and Space values, and can be treated as such.
Next, the expression uses let! to 'generate' a Space list value, which will contain other values (if any; the generated list could be empty).
This gives you all the building blocks required to generate a list that contains at least the two desired values, as well as other values. To create such a list, you can cons (::) the two 'known' values onto the list, and then shuffle the result for good measure.
The final expression in the gen computation expression then returns a four-element tuple. The type of that expression is Gen<Piece * Space * Space list * Status>. It can be turned into an Arbitrary<Piece * Space * Space list * Status> by Arb.fromGen, and further piped into Prop.forAll.
This addresses the problem that the moving checker retains set count property throws exceptions internally.
This, incidentally, demonstrates that the property is falsifiable:
Test 'Ploeh.StackOverflow.Q38857462.Properties.moving checker retains set count' failed: FsCheck.Xunit.PropertyFailedException :
Falsifiable, after 70 tests (0 shrinks) (StdGen (1318556550,296190265)):
Original:
<null>
(Black (BlackKing,(-1, 1)), Available (0, 0),
[Occupied (Red (RedSoldier,(-1, 0))); Available (0, 0);
Occupied (Black (BlackKing,(-1, 1))); Available (0, 0)], RedsTurn)
Whether this is a problem with the test or with the implementation is a different question...
How do I include multiple arguments for List.filter?
I need to add some parameters to a function that serves as a predicate for filtering a list.
In F#, the List.filter accepts just one argument. However, I need to add multiple arguments for my predicate to work.
In my case, I need to add sourceX and sourceY as parameters:
let jumpOptions space =
match space with
| Allocated p -> match p with
| Red (ch,xy) -> xy = (sourceX + 1, sourceY - 1) ||
xy = (sourceX - 1, sourceY - 1)
| Black (ch,xy) -> xy = (sourceX + 1, sourceY + 1) ||
xy = (sourceX - 1, sourceY + 1)
| _ -> false
let jumpsForSoldier piece positions =
match piece with
| Black (ch,pos) -> positions |> List.filter jumpOptions
| Red (ch,pos) -> positions |> List.filter jumpOptions
In conclusion, I want to keep the elements within my list pure. Hence, I do not want to bundle each element within my list with other values just to satisfy a filter function.
Any guidance?
Appendix:
open NUnit.Framework
open FsUnit
(* Types *)
type Black = BlackKing | BlackSoldier
type Red = RedKing | RedSoldier
type Coordinate = int * int
type Piece =
| Black of Black * Coordinate
| Red of Red * Coordinate
type Space =
| Allocated of Piece
| Available of Coordinate
type Status =
| BlacksTurn | RedsTurn
| BlackWins | RedWins
(* Private *)
let private black coordinate = Allocated (Black (BlackSoldier , coordinate))
let private red coordinate = Allocated (Red (RedSoldier , coordinate))
let private yDirection = function
| Black _ -> -1
| Red _ -> 1
let private toAvailable = function
| Available pos -> true
| _ -> false
let available positions = positions |> List.filter toAvailable
let private availableSelection = function
| Available pos -> Some pos
| Allocated _ -> None
let private availablePositions positions =
positions |> List.filter toAvailable
|> List.choose availableSelection
let private allocatedSelection = function
| Allocated p -> match p with
| Red (ch,xy) -> Some xy
| Black (ch,xy) -> Some xy
| _ -> None
let private allocatedPositions positions =
positions |> List.filter toAvailable
|> List.choose allocatedSelection
let private getCoordinate = function
| Available xy -> Some xy
| _ -> None
let coordinateOf = function
| Black (checker , pos) -> pos
| Red (checker , pos) -> pos
let jumpOptions space =
match space with
| Allocated p -> match p with
| Red (ch,xy) -> let sourceX, sourceY = coordinateOf source
xy = (sourceX + 1, sourceY - 1) ||
xy = (sourceX - 1, sourceY - 1)
| Black (ch,xy) -> let sourceX, sourceY = coordinateOf p
xy = (sourceX + 1, sourceY + 1) ||
xy = (sourceX - 1, sourceY + 1)
| _ -> false
let jumpsForSoldier piece positions =
match piece with
| Black (ch,pos) -> positions |> List.filter jumpOptions
| Red (ch,pos) -> positions |> List.filter jumpOptions
let private isKing piece =
match piece with
| Black (checker , _) -> match checker with
| BlackSoldier -> false
| BlackKing -> true
| Red (checker , _) -> match checker with
| RedSoldier -> false
| RedKing -> true
(* Public *)
let startGame () =
[ red (0,0); red (2,0); red (4,0); red (6,0)
red (1,1); red (3,1); red (5,1); red (7,1)
red (0,2); red (2,2); red (4,2); red (6,2)
Available (1,3); Available (3,3); Available (5,3); Available (7,3)
Available (0,4); Available (2,4); Available (4,4); Available (6,4)
black (1,5); black (3,5); black (5,5); black (7,5)
black (0,6); black (2,6); black (4,6); black (6,6)
black (1,7); black (3,7); black (5,7); black (7,7) ] , BlacksTurn
let optionsFor piece positions =
let sourceX , sourceY = coordinateOf piece
let optionsForSoldier =
(fun pos -> pos = ((sourceX - 1) , (sourceY + (piece |> yDirection) )) ||
pos = ((sourceX + 1) , (sourceY + (piece |> yDirection) )))
let optionsForKing =
(fun pos -> pos = ((sourceX - 1) , (sourceY + 1 )) ||
pos = ((sourceX + 1) , (sourceY + 1 )) ||
pos = ((sourceX - 1) , (sourceY - 1 )) ||
pos = ((sourceX + 1) , (sourceY - 1 )))
match piece |> isKing with
| false -> positions |> availablePositions
|> List.filter optionsForSoldier
| true -> positions |> availablePositions
|> List.filter optionsForKing
let move piece destination positions =
let rec movePiece positions destinationXY =
let foundPiece = positions |> List.filter (fun space -> space = Allocated piece)
|> List.head
match foundPiece with
| Allocated (Black (ch, xy)) -> (positions |> List.filter (fun space -> space <> Allocated (Black (ch, xy)))
|> List.filter (fun space -> space <> destination))
# [Available (xy) ; (Allocated (Black (ch, destinationXY)))]
| Allocated (Red (ch, xy)) -> (positions |> List.filter (fun space -> space <> Allocated (Red (ch, xy)))
|> List.filter (fun space -> space <> destination))
# [Available (xy) ; (Allocated (Red (ch, destinationXY)))]
| _ -> positions
let options = optionsFor piece positions
let canMoveTo = (fun target -> options |> List.exists (fun xy -> xy = target))
match getCoordinate destination with
| Some target -> match canMoveTo target with
| true -> movePiece positions target
| false -> positions
| None -> positions
(* Tests *)
[<Test>]
let ``get jump options for red soldier`` () =
// Setup
let redPiece = Red ( RedSoldier , (0,2) )
let blackPiece = Black ( BlackSoldier , (1,3) )
let positions = [Allocated redPiece; Available (2,2); Available (4,2); Available (6,2)
Allocated blackPiece; Available (3,3); Available (5,3); Available (7,3)
Available (0,4); Available (2,4); Available (4,4); Available (6,4)]
// Test
positions |> jumpsForSoldier redPiece
|> should equal [Allocated blackPiece]
You can have as many parameters as you want, then partially apply the function to all of them but one, and pass the result to List.filter.
let jumpOptions sourceX sourceY space = ...
...
positions |> List.filter (jumpOptions 5 42)
Read more on partial application here.
You could extract the required info using pattern matching and use partial application (code edited after TQBF comments)
let jumpOptions (sourceX, sourceY) = function
Allocated (Red (_, (x, y)) as p)
| Allocated (Black (_, (x, y)) as p) when abs (sourceX - x) = 1
-> y = sourceY - yDirection p
| _ -> false
let jumpsForSoldier = function
Red (_, pos)
| Black (_, pos) -> List.filter (jumpOptions pos)
I have worked on this quite a while and is stuck with this bug.
We have build a ray-tracer in F# for a school project. (Link explaining Ray tracer: https://blog.frogslayer.com/kd-trees-for-faster-ray-tracing-with-triangles/)
We have a made hit function for triangles, boundingboxes, a KD tree to handle figures with thousands of triangles, such as the Stanford Bunny and a traverse algorithm for the KD tree.
We have debugged both the creation of the KD tree, made sure to add a epsilon value for float points and checked that duplicates between the boundingboxes are not removed. We are certain that we split the list of shapes in the scene corretly, but we get "holes" in the figure we try to render.
This is our KD tree implementation and I've attached pictures of the holes:
Stanford Bunny
Helix
module TmKdtree
open Point
open Shapes
type BoundingBox = BasicShape.BoundingBox
type Shape = BasicShape.Shape
type TmKdtree =
| Leaf of BasicShape.Triangle list * BoundingBox
| Node of BasicShape.Triangle list * TmKdtree * TmKdtree * BoundingBox * (string*Point)
let epsilon = 0.001
//Making a boundingbox for the KD-tree, by finding max H point in the boundingboxlist and min l point in the boundingbox list.
let mkKdBbox (shapes : BasicShape.Triangle list) : BoundingBox =
let shapeX = List.map(fun x -> x:> Shape) shapes
let sbbox = List.map (fun (c:Shape) -> c.getBounding().Value) shapeX
let bL = List.map (fun (b:BasicShape.BoundingBox) -> b.getL) sbbox
let bH = List.map (fun (b:BasicShape.BoundingBox) -> b.getH) sbbox
let minX = List.minBy (fun x -> Point.getX x) bL
let minY = List.minBy (fun x -> Point.getY x) bL
let minZ = List.minBy (fun x -> Point.getZ x) bL
let maxX = List.maxBy (fun x -> Point.getX x) bH
let maxY = List.maxBy (fun x -> Point.getY x) bH
let maxZ = List.maxBy (fun x -> Point.getZ x) bH
{p1=(mkPoint (Point.getX minX - epsilon) (Point.getY minY - epsilon) (Point.getZ minZ - epsilon) )
; p2=(mkPoint (Point.getX maxX + epsilon) (Point.getY maxY + epsilon) (Point.getZ maxZ + epsilon) )}
//Splitting existing boundingbox according to left and right list of shapes
let BoundingBoxL (bbox:BoundingBox) axis midp : BoundingBox =
match axis with
| "x" -> {p1 = bbox.getL - epsilon; p2 = Point.mkPoint ((Point.getX midp)) ((Point.getY (bbox.getH))) ((Point.getZ (bbox.getH))) + epsilon}
| "y" -> {p1 = bbox.getL - epsilon; p2 = Point.mkPoint (Point.getX (bbox.getH)) ((Point.getY midp)+epsilon) ((Point.getZ (bbox.getH))) + epsilon }
| "z" -> {p1 = bbox.getL - epsilon; p2 = Point.mkPoint (Point.getX (bbox.getH)) (Point.getY (bbox.getH)) (Point.getZ midp) + epsilon}
let BoundingBoxR (bbox:BoundingBox) axis midp : BoundingBox =
match axis with
| "x" -> {p1 = (Point.mkPoint (Point.getX midp) (Point.getY (bbox.getL)) (Point.getZ (bbox.getL))) - epsilon; p2 = bbox.getH + epsilon}
| "y" -> {p1 = (Point.mkPoint (Point.getX (bbox.getL)) (Point.getY midp) (Point.getZ (bbox.getL))) - epsilon; p2 = bbox.getH + epsilon}
| "z" -> {p1 = (Point.mkPoint (Point.getX (bbox.getL)) (Point.getY (bbox.getL)) (Point.getZ midp)) - epsilon; p2 = bbox.getH + epsilon}
//Get left node
let getLeft s =
match s with
| Node(_,l,_,_,_) -> l
| Leaf(_,_) as leaf -> leaf
let getRight s =
match s with
| Node(_,_,r,_,_) -> r
| Leaf(_,_) as leaf -> leaf
//Get the triangle list
let getShapes s =
match s with
| Node(b,_,_,_,_) -> b
| Leaf(b,_) -> b
let getAxis s =
match s with
| Node(_,_,_,_,a) -> a
| Leaf(_,_) -> failwith "leaf ramt af axis"
//Get bounding box
let getBox s =
match s with
| Node(_,_,_,b,_) -> Some b
| Leaf(_,b) -> Some b
let closestHit (triList : BasicShape.Triangle list) ray =
let sndRects = List.map(fun x -> x:> Shape) triList
let min = List.map(fun (x:Shape) -> x.hit ray) sndRects |> List.choose id
match min with
|[] -> None
|_ -> Some(List.minBy (fun (di, nV, mat) -> di) min)
let searchLeaf leaf ray t' =
match leaf with
| Leaf(s,_) -> let hit = closestHit s ray
match hit with
|Some(f,_,_) -> if (f<t') then Some hit else None
|None -> None
| Node(_,_,_,_,_) -> failwith "Expected leaf"
let order(d, left, right) =
if d > 0.0
then (left, right)
else (right, left)
let rec search node ray t t' =
match node with
|Leaf(_,_) -> searchLeaf node ray t'
|Node(_,_,_,_,a') ->
let direction = Ray.getDirection ray (fst a')
let origin = Ray.getOrigin ray (fst a')
let nodeValue = Point.getFromAxis (snd a') (fst a')
if(direction) = 0.0 then
printfn("%s") "flatsite"
if(origin <= nodeValue) then search (getLeft node) ray t t'
else search (getRight node) ray t t'
else
let tHit = (nodeValue - origin) / direction
let (fst, snd) = order(direction,getLeft node, getRight node)
if tHit <= t || tHit < 0.0 then
search snd ray t t'
else if tHit >= t' then
search fst ray t t'
else
match search fst ray t tHit with
|Some hit -> Some hit
|_ -> search snd ray tHit t'
let traverse tree ray (bawx:BasicShape.BoundingBox) =
match(bawx).hit(ray) with
|Some(t,t') -> search tree ray t t'
|None -> None
//Finding the midpoint in the triangles in Shapes-list - we do this (recursively) to find out what axis to split
let rec mkTmKdtree (shapes : BasicShape.Triangle list) (box:BasicShape.BoundingBox) =
//Finding biggest dimension in the shapes list
let axis = snd (box.getLongestAxis)
let axisMidPoint =
let midPoint = List.fold (fun acc (ele:BasicShape.Triangle) -> (acc + ele.getMidPoint())) (Point.mkPoint 0.0 0.0 0.0) shapes
let avgMid = midPoint / float(shapes.Length)
avgMid
let rec largerThanSplit (xs:BasicShape.Triangle list) =
let results = List.choose(fun (elem:BasicShape.Triangle) ->
match axis with
|"x" -> if (Point.getX (elem.getMidPoint())) >= (Point.getX axisMidPoint) then Some elem else None
|"y" -> if (Point.getY (elem.getMidPoint())) >= (Point.getY axisMidPoint) then Some elem else None
|"z" -> if (Point.getZ (elem.getMidPoint())) >= (Point.getZ axisMidPoint) then Some elem else None) xs
results
let rec lessThanSplit (xs:BasicShape.Triangle list) =
let results = List.choose(fun (elem:BasicShape.Triangle) ->
match axis with
|"x" -> if ((Point.getX (elem.getMidPoint())) <= (Point.getX (axisMidPoint))) then Some elem else None
|"y" -> if ((Point.getY (elem.getMidPoint())) <= (Point.getY (axisMidPoint))) then Some elem else None
|"z" -> if ((Point.getZ (elem.getMidPoint())) <= (Point.getZ (axisMidPoint))) then Some elem else None) xs
results
//Creating the left and right list from the above
let rightTest = largerThanSplit shapes
let leftTest = lessThanSplit shapes
//If one of the trees are empty, we add make left and right equivelant.
let left = if(leftTest.IsEmpty && rightTest.Length > 0) then rightTest else leftTest
let right = if(rightTest.IsEmpty && leftTest.Length > 0) then leftTest else rightTest
//Check for duplicates among the lists.
if(((float(left.Length+right.Length-shapes.Length)/float(shapes.Length)) < 0.4) && left.Length <> shapes.Length && right.Length<>shapes.Length) then
let leftTree = mkTmKdtree left (BoundingBoxL box axis axisMidPoint)
let rightTree = mkTmKdtree right (BoundingBoxR box axis axisMidPoint)
Node(shapes,leftTree, rightTree, (box),(axis,axisMidPoint))
else Leaf(shapes, (box))
Thank you for the responses! I turned out that the bug was in our reflections of the figures, and had nothing to do with the data structure of the program.
But thank you anyway! :-)