Skip to content

Commit 3f0e97d

Browse files
committed
Merge branch 'article3'
2 parents cc32a7f + 5098bb6 commit 3f0e97d

File tree

13 files changed

+407
-112
lines changed

13 files changed

+407
-112
lines changed

MattEland.FSharpGeneticAgorithm.ConsoleTestApp/Display.fs

+31-8
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,49 @@
22

33
open System
44
open MattEland.FSharpGeneticAlgorithm.Logic.World
5+
open MattEland.FSharpGeneticAlgorithm.Logic.Simulator
56

67
let printCell char isLastCell =
78
if isLastCell then
89
printfn "%c" char
910
else
1011
printf "%c" char
1112

12-
let displayWorld (world: World) =
13+
let displayWorld (world: World) turnsRemaining =
14+
printfn ""
15+
printfn "%i turn(s) remaining" turnsRemaining
1316
printfn ""
1417

1518
for y in 1..world.MaxX do
16-
for x in 1..world.MaxY do
17-
// Determine which character should exist in this line
18-
let char = world |> getCharacterAtCell(x,y)
19-
printCell char (x = world.MaxX)
19+
printf " " // Indent slightly so it is a bit more readable
20+
21+
for x in 1..world.MaxY do
22+
// Determine which character should exist in this line
23+
let char = world |> getCharacterAtCell(x,y)
24+
25+
// Let's set the overall color
26+
match char with
27+
| '.' -> Console.ForegroundColor <- ConsoleColor.DarkGreen
28+
| 't' -> Console.ForegroundColor <- ConsoleColor.Green
29+
| 'a' -> Console.ForegroundColor <- ConsoleColor.DarkYellow
30+
| 'S' | 's' -> Console.ForegroundColor <- ConsoleColor.Yellow
31+
| 'D' -> Console.ForegroundColor <- ConsoleColor.Red
32+
| 'R' -> Console.ForegroundColor <- ConsoleColor.Magenta
33+
| _ -> Console.ForegroundColor <- ConsoleColor.Gray
34+
35+
printCell char (x = world.MaxX)
36+
37+
// Ensure we go back to standard format
38+
Console.ForegroundColor <- ConsoleColor.White
2039

21-
let getUserInput(world: World): ConsoleKeyInfo =
22-
displayWorld world
40+
let getUserInput(state: GameState): ConsoleKeyInfo =
41+
displayWorld state.World state.TurnsLeft
2342
printfn ""
24-
printfn "Press Arrow Keys to move, R to regenerate, or X to exit"
43+
44+
match state.SimState with
45+
| Simulating -> printfn "Press Arrow Keys to move, R to regenerate, or X to exit"
46+
| Won -> printfn "Squirrel Returned Home with the Acorn! Press R to reload or X to exit."
47+
| Lost -> printfn "Simulation ended: Squirrel died. Press R to reload or X to exit."
2548

2649
let key = Console.ReadKey(true)
2750
Console.Clear()

MattEland.FSharpGeneticAgorithm.ConsoleTestApp/Program.fs

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
open System
2-
open MattEland.FSharpGeneticAlgorithm.Logic.World
32
open MattEland.FSharpGeneticAlgorithm.Logic.Simulator
3+
open MattEland.FSharpGeneticAlgorithm.Logic.WorldGeneration
4+
open MattEland.FSharpGeneticAlgorithm.Logic.Commands
45
open MattEland.FSharpGeneticAlgorithm.ConsoleTestApp.Display
56

67
type Command =
@@ -30,20 +31,19 @@ let main argv =
3031
let r = Random()
3132
fun max -> (r.Next max) + 1
3233

33-
let world = makeWorld 13 13 getRandomNumber
34+
let world = makeTestWorld false
3435

35-
let mutable state = { World = world; Player = world.Squirrel }
36+
let mutable state = { World = world; SimState = Simulating; TurnsLeft=30}
3637
let mutable simulating: bool = true
3738

3839
while simulating do
39-
let player = state.World.Squirrel
40-
let userCommand = getUserInput(state.World) |> tryParseInput
40+
let userCommand = getUserInput(state) |> tryParseInput
4141

4242
match userCommand with
4343
| Some command ->
4444
match command with
4545
| Exit -> simulating <- false
46-
| Action gameCommand -> state <- playTurn state player getRandomNumber gameCommand
46+
| Action gameCommand -> state <- playTurn state getRandomNumber gameCommand
4747
| None -> printfn "Invalid input"
4848

4949
0 // return an integer exit code

MattEland.FSharpGeneticAlgorithm.Logic/Actors.fs

+5-3
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ type ActorKind =
1010
| Doggo
1111

1212
type Actor =
13-
{ Pos : WorldPos
14-
ActorKind : ActorKind }
13+
{ Pos : WorldPos;
14+
ActorKind : ActorKind;
15+
IsActive : bool
16+
}
1517

1618
let getChar actor =
1719
match actor.ActorKind with
18-
| Squirrel _ -> 'S'
20+
| Squirrel hasAcorn -> match hasAcorn with | true -> 'S' | false -> 's'
1921
| Tree _ -> 't'
2022
| Acorn _ -> 'a'
2123
| Rabbit _ -> 'R'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module MattEland.FSharpGeneticAlgorithm.Logic.Commands
2+
3+
type GameCommand =
4+
| MoveLeft | MoveRight | MoveUp | MoveDown
5+
| MoveUpLeft | MoveUpRight | MoveDownLeft | MoveDownRight
6+
| Wait
7+
| Restart

MattEland.FSharpGeneticAlgorithm.Logic/MattEland.FSharpGeneticAlgorithm.Logic.fsproj

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
<Compile Include="WorldPos.fs" />
99
<Compile Include="Actors.fs" />
1010
<Compile Include="World.fs" />
11+
<Compile Include="WorldGeneration.fs" />
12+
<Compile Include="Commands.fs" />
1113
<Compile Include="Simulator.fs" />
1214
</ItemGroup>
1315

MattEland.FSharpGeneticAlgorithm.Logic/Simulator.fs

+136-39
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,151 @@
33
open MattEland.FSharpGeneticAlgorithm.Logic.WorldPos
44
open MattEland.FSharpGeneticAlgorithm.Logic.World
55
open MattEland.FSharpGeneticAlgorithm.Logic.Actors
6+
open MattEland.FSharpGeneticAlgorithm.Logic.Commands
7+
open MattEland.FSharpGeneticAlgorithm.Logic.WorldGeneration
68

7-
type GameState = { World : World; Player : Actor }
9+
type SimulationState = Simulating | Won | Lost
810

9-
let isValidPos pos (world: World): bool =
10-
pos.X >= 1 && pos.Y >= 1 && pos.X <= world.MaxX && pos.Y <= world.MaxY
11+
type GameState = { World : World; SimState: SimulationState; TurnsLeft: int}
1112

12-
let hasObstacle pos (world: World) : bool =
13-
world.Actors
14-
|> Seq.exists(fun actor -> pos = actor.Pos)
13+
let canEnterActorCell actor target =
14+
match target with
15+
| Rabbit | Squirrel _ -> actor = Doggo // Dog can eat the squirrel or rabbit
16+
| Doggo _ -> false // Nobody bugs the dog
17+
| Tree _ -> actor = Squirrel true // Only allow if squirrel has an acorn
18+
| Acorn _ -> actor = Squirrel false // Only allow if squirrel w/o acorn
1519

16-
let moveActor world actor xDiff yDiff =
17-
let pos = newPos (actor.Pos.X + xDiff) (actor.Pos.Y + yDiff)
20+
let moveActor state actor pos =
21+
let world = state.World
1822

19-
if (isValidPos pos world) && not (hasObstacle pos world) then
23+
let performMove =
2024
let actor = { actor with Pos = pos }
2125
match actor.ActorKind with
22-
| Squirrel _ -> { world with Squirrel = actor }
23-
| Tree -> { world with Tree = actor }
24-
| Acorn -> { world with Acorn = actor }
25-
| Rabbit -> { world with Rabbit = actor }
26-
| Doggo -> { world with Doggo = actor }
26+
| Squirrel _ -> { state with World={world with Squirrel = actor }}
27+
| Tree -> { state with World={world with Tree = actor }}
28+
| Acorn -> { state with World={world with Acorn = actor }}
29+
| Rabbit -> { state with World={world with Rabbit = actor }}
30+
| Doggo -> { state with World={world with Doggo = actor }}
31+
32+
let handleDogMove state otherActor =
33+
if otherActor.ActorKind = Rabbit then
34+
{state with World = {world with
35+
Rabbit = {world.Rabbit with IsActive = false}
36+
Doggo = {world.Doggo with Pos = pos}
37+
}}
38+
else
39+
{state with SimState = Lost; World = {world with
40+
Squirrel = {world.Squirrel with IsActive = false}
41+
Doggo = {world.Doggo with Pos = pos}
42+
}
43+
}
44+
45+
let handleSquirrelMove otherActor hasAcorn =
46+
if not hasAcorn && otherActor.ActorKind = Acorn && otherActor.IsActive then
47+
// Moving to the acorn for the first time should give the squirrel the acorn
48+
{state with World =
49+
{
50+
world with
51+
Squirrel = {ActorKind = Squirrel true; Pos = pos; IsActive = true}
52+
Acorn = {world.Acorn with IsActive = false}
53+
}
54+
}
55+
else if hasAcorn && otherActor.ActorKind = Tree then
56+
// Moving to the tree with the acorn - this should win the game
57+
{
58+
state with SimState = Won; World = {
59+
world with Squirrel = {ActorKind = Squirrel true; Pos = pos; IsActive = true}
60+
}
61+
}
62+
else
63+
performMove
64+
65+
let target = tryGetActor(pos.X, pos.Y) world
66+
67+
match target with
68+
| None -> performMove
69+
| Some otherActor ->
70+
if otherActor <> actor && canEnterActorCell actor.ActorKind otherActor.ActorKind then
71+
match actor.ActorKind with
72+
| Doggo -> handleDogMove state otherActor
73+
| Squirrel hasAcorn -> handleSquirrelMove otherActor hasAcorn
74+
| _ -> performMove
75+
else
76+
state
77+
78+
let getCandidates (current: WorldPos, world: World, includeCenter: bool): WorldPos seq =
79+
let mutable candidates: WorldPos seq = Seq.empty
80+
for x in -1 .. 1 do
81+
for y in -1 .. 1 do
82+
if (includeCenter || x <> y || x <> 0) then
83+
// Make sure we're in the world boundaries
84+
let candidatePos = {X=current.X + x; Y=current.Y + y}
85+
if isValidPos candidatePos world then
86+
candidates <- Seq.append candidates [candidatePos]
87+
candidates
88+
89+
let moveRandomly state actor getRandomNumber =
90+
let current = actor.Pos
91+
let movedPos = getCandidates(current, state.World, false)
92+
|> Seq.sortBy(fun _ -> getRandomNumber 1000)
93+
|> Seq.head
94+
95+
moveActor state actor movedPos
96+
97+
let simulateDoggo (state: GameState) =
98+
let doggo = state.World.Doggo
99+
let rabbit = state.World.Rabbit
100+
let squirrel = state.World.Squirrel
101+
102+
// Eat any adjacent actor
103+
if rabbit.IsActive && isAdjacentTo doggo.Pos rabbit.Pos then
104+
moveActor state doggo rabbit.Pos
105+
else if squirrel.IsActive && isAdjacentTo doggo.Pos squirrel.Pos then
106+
moveActor state doggo squirrel.Pos
27107
else
28-
world
108+
state
109+
110+
let decreaseTimer (state: GameState) =
111+
if state.SimState = Simulating then
112+
if state.TurnsLeft > 0 then
113+
{state with TurnsLeft = state.TurnsLeft - 1}
114+
else
115+
{state with TurnsLeft = 0; SimState = Lost}
116+
else
117+
state
118+
119+
let simulateActors (state: GameState) getRandomNumber =
120+
moveRandomly state state.World.Rabbit getRandomNumber
121+
|> simulateDoggo
122+
|> decreaseTimer
29123

30-
type GameCommand =
31-
| MoveLeft | MoveRight
32-
| MoveUp | MoveDown
33-
| MoveUpLeft | MoveUpRight
34-
| MoveDownLeft | MoveDownRight
35-
| Wait
36-
| Restart
124+
let handlePlayerCommand state command =
125+
let player = state.World.Squirrel
126+
let xDelta =
127+
match command with
128+
| MoveLeft | MoveDownLeft | MoveUpLeft -> -1
129+
| MoveRight | MoveDownRight | MoveUpRight -> 1
130+
| _ -> 0
131+
let yDelta =
132+
match command with
133+
| MoveUpLeft | MoveUp | MoveUpRight -> -1
134+
| MoveDownLeft | MoveDown | MoveDownRight -> 1
135+
| _ -> 0
37136

38-
let playTurn state player getRandomNumber command =
137+
let movedPos = {X=player.Pos.X + xDelta; Y=player.Pos.Y + yDelta}
138+
139+
if isValidPos movedPos state.World then
140+
moveActor state player movedPos
141+
else
142+
state
143+
144+
let playTurn state getRandomNumber command =
39145
let world = state.World
40146
match command with
41-
| MoveLeft -> { state with World = moveActor world player -1 0 }
42-
| MoveRight -> { state with World = moveActor world player 1 0 }
43-
| MoveUp -> { state with World = moveActor world player 0 -1 }
44-
| MoveDown -> { state with World = moveActor world player 0 1 }
45-
| MoveUpLeft -> { state with World = moveActor world player -1 -1 }
46-
| MoveUpRight -> { state with World = moveActor world player 1 -1 }
47-
| MoveDownLeft -> { state with World = moveActor world player -1 1 }
48-
| MoveDownRight -> { state with World = moveActor world player 1 1 }
49-
| Wait ->
50-
printfn "Time Passes..."
51-
state
52-
| Restart ->
53-
let world = makeWorld 13 13 getRandomNumber
54-
{ World = world; Player = world.Squirrel }
55-
56-
// TODO: I'll need a way of simulating an actor's turn
147+
| Restart -> { World = makeWorld world.MaxX world.MaxY getRandomNumber; SimState = Simulating; TurnsLeft = 30 }
148+
| _ ->
149+
match state.SimState with
150+
| Simulating ->
151+
let newState = handlePlayerCommand state command
152+
simulateActors newState getRandomNumber
153+
| _ -> state

0 commit comments

Comments
 (0)