-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMonsterGame.elm
141 lines (121 loc) · 4.2 KB
/
MonsterGame.elm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
module MonsterGame exposing (..)
import TreasureGame exposing (Prop(Chest), Dungeon, Explorer,
explorer0, dungeon0, initDungeon, runDungeon, drawDungeon)
import TreasureGame_BT exposing (Simulation, sim0, initTree)
import ChaseEvade exposing (chase, evade)
import ClassicalEngine exposing (Actor, drawVehicle)
import PathFinding exposing (AStarState,
state0, initSearch, aStar, drawPath, inf)
import PathFollowing exposing (Exploration(..), maxA, maxV)
import BehaviourTree exposing (Behaviour)
import HeatMap exposing (heatNav, heatProx)
import Grid exposing (Grid, Point,
manhattan, neighbors4, neighbors20, screen2grid, grid2screen)
import Vec2 exposing (..)
import Random exposing (Generator)
import Time exposing (Time)
import Collage exposing (Form)
import Color exposing (orange, lightGreen, darkOrange)
import Text
import Array exposing (Array)
--- STRUCTURES ---
type alias Game =
{ dungeon : Dungeon
, script : Behaviour Dungeon
, score : Int
, reset : Time
, goalMap : Array Float
, monsterMap : Array Float
, prevMonPoint : Point
}
game0 : Game
game0 =
{ dungeon = dungeon0
, script = BehaviourTree.empty
, score = 0
, reset = resetTime
, goalMap = Array.empty
, monsterMap = Array.empty
, prevMonPoint = (0, 0)
}
--- CONSTANTS ---
resetTime : Time
resetTime = Time.second * 5
goalHeat : Float
goalHeat = 1
monsterHeat : Float
monsterHeat = 1
--- BEHAVIOR ---
heatSeek : Grid -> Array Float -> Array Float -> Actor etc -> Actor etc
heatSeek grid heatMap dangerMap actor = let
(x, y) = actor.pos
p = screen2grid (clamp -300 300 x, clamp -300 300 y) grid
hotNeighbors = neighbors20 p grid
|> List.filterMap ( \(point, dist) -> let i = Grid.index point grid in
Array.get i heatMap `Maybe.andThen` (\good ->
Array.get i dangerMap `Maybe.andThen` (\bad ->
Just (good - bad, point, dist)
)))
(_, localGoal, _) = List.maximum hotNeighbors |> Maybe.withDefault (0, p, 0)
(_, target) = hotNeighbors
|> List.filterMap ( \(good, point, _) -> case manhattan point p of
1 -> Just (good - toFloat (manhattan point localGoal), point)
_ -> Nothing
)
|> List.maximum |> Maybe.withDefault (0, p)
in
chase (maxV (Grid.get p grid)) maxA (grid2screen target grid) actor
--- SIMULATION ---
genGame : Generator Game
genGame =
Random.map (\d -> { game0 | dungeon = d, script = initTree,
prevMonPoint = (-1, -1) }) initDungeon
controlMonster : Vec2 -> Game -> Game
controlMonster pointer game = let
{dungeon} = game
{floor, monster} = dungeon
start = screen2grid monster.pos floor
goal = screen2grid pointer floor
in
{ game | dungeon = { dungeon | monster = { monster | state = Plotting goal }}}
run : Time -> Game -> Game
run t game = if game.score /= 0 then { game | reset = game.reset - t } else
let
(new_dungeon, _, new_script) =
BehaviourTree.step game.script (runDungeon t game.dungeon)
{floor, monster, explorer} = new_dungeon
eMaxV = maxV (Grid.get (screen2grid explorer.pos floor) floor)
mp = screen2grid monster.pos floor
goalMap = case explorer.state of
Plotting goal -> heatNav floor goalHeat goal
_ -> game.goalMap
monsterMap = if mp == game.prevMonPoint
then game.monsterMap
else heatProx floor monsterHeat mp
new_e = heatSeek floor goalMap monsterMap <| case explorer.state of
Plotting goal -> { explorer | state = Arriving goal }
_ -> explorer
new_game = { game
| dungeon = { new_dungeon | explorer = new_e }
, script = new_script
, goalMap = goalMap
, monsterMap = monsterMap
, prevMonPoint = mp
}
in
if dist monster.pos explorer.pos < 16
then { new_game | score = -1 }
else if List.all (fst >> (/=) Chest) new_dungeon.loot
then { new_game | score = 1 }
else new_game
--- DRAWING ---
drawGame : Game -> List Form
drawGame {dungeon, score, goalMap} = drawDungeon dungeon
++ drawVehicle orange dungeon.monster
++ case dungeon.monster.state of
Seeking path -> [drawPath path dungeon.floor]
_ -> []
++ [Collage.text <| Text.bold <| Text.height 32 <| case score of
1 -> Text.fromString "EXPLORER WINS" |> Text.color lightGreen
(-1) -> Text.fromString "MONSTER WINS" |> Text.color darkOrange
_ -> Text.fromString ""]