-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGame.fs
199 lines (185 loc) · 6.77 KB
/
Game.fs
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
module Game
open System
let private nextRoom width room dir =
match dir with
| Up -> room - width
| Down -> room + width
| Left -> room - 1
| Right -> room + 1
let private isValidDir (game: Game) room dir =
let row = room / game.Width
let col = room % game.Width
match dir with
| Up -> row > 0
| Down -> row < game.Height-1
| Left -> col > 0
| Right -> col < game.Width-1
let private canMove game room dir =
if isValidDir game room dir then
match dir with
| Up -> not game.Rooms.[room].N
| Down -> not game.Rooms.[room+game.Width].N
| Left -> not game.Rooms.[room].W
| Right -> not game.Rooms.[room+1].W
else false
let toCharCoord (config: Config) room =
(room%config.RoomWidth*2 + 1, room/config.RoomWidth*2 + 1)
let toAreaCharCoords config room =
let x, y = toCharCoord config room
let minX, maxX = max (x-1) 0, min (x+1) (config.CharWidth-1)
let minY, maxY = max (y-1) 0, min (y+1) (config.CharHeight-1)
[minY..maxY] |> List.collect (fun y -> [minX..maxX] |> List.map (fun x -> (x, y)))
let private updateVisibleCoords game =
let rec trace room acc dir =
if canMove game room dir then
let next = nextRoom game.Width room dir
trace next (next :: acc) dir
else acc
let tracePlayer = trace game.Player []
let visibleCoords = lazy (
let visibleRooms = [
game.Player
yield! tracePlayer Up
yield! tracePlayer Down
yield! tracePlayer Left
yield! tracePlayer Right
]
visibleRooms |> List.collect (toAreaCharCoords game.Config) |> set
)
match game.Config.Visibility with
| Full -> game
| Explored -> { game with VisibleCoords = Set.union game.VisibleCoords visibleCoords.Value }
| Sight -> { game with VisibleCoords = visibleCoords.Value }
let private renderWalls (maze: char array array) =
let space = ' '
let walls =
" ─│┌" +
"──┐┬" +
"│└│├" +
"┘┴┤┼"
maze |> Array.mapi (fun y row ->
row |> Array.mapi (fun x c ->
if c = space then c
else
let e = if x < row.Length-1 && maze.[y].[x+1] <> space then 1 else 0
let s = if y < maze.Length-1 && maze.[y+1].[x] <> space then 2 else 0
let w = if x > 0 && maze.[y].[x-1] <> space then 4 else 0
let n = if y > 0 && maze.[y-1].[x] <> space then 8 else 0
walls.[n+e+s+w]
)
)
let renderMaze game =
let wall = '█'
let space = ' '
let toChar isWall = if isWall then wall else space
let line roomRow f = roomRow |> Array.collecti f
let maze =
game.Rooms
|> Array.indexed
|> Array.chunkBySize game.Width
|> Array.collecti (fun row roomRow -> [|
line roomRow (fun col (_, room) -> [|
wall
toChar room.N
if col = game.Width-1 then
wall
|])
line roomRow (fun col (i, room) -> [|
toChar (room.W && i <> game.Start)
space
if col = game.Width-1 then
toChar (i <> game.Finish)
|])
if row = game.Height - 1 then
line roomRow (fun col _ -> [|
wall
wall
if col = game.Width-1 then
wall
|])
|])
{ game with RenderedMaze = renderWalls maze }
let private getViewCenteredOn (config: Config) viewSize (x, y) =
let viewWidth, viewHeight = viewSize
let viewLeft = x - viewWidth/2 |> clamp (0, max 0 (config.CharWidth - viewWidth))
let viewTop = y - viewHeight/2 |> clamp (0, max 0 (config.CharHeight - viewHeight))
(viewLeft, viewTop)
let newGame viewSize config =
let (width, height) = (config.RoomWidth, config.RoomHeight)
let rooms = Array.init (width*height) (fun _ -> { N = true; W = true })
let wallsToRemove = [|
for room in Seq.init (width*height) id do
if room >= width then
(room, N)
if room % width > 0 then
(room, W)
|]
let roomNodes = Array.init (width*height) (fun _ -> UnionFindNode())
for (room, wall) in shuffle wallsToRemove do
let otherRoom =
match wall with
| N -> room - width
| W -> room - 1
if roomNodes.[room].Union(roomNodes.[otherRoom]) then
rooms.[room] <-
match wall with
| N -> { rooms.[room] with N = false }
| W -> { rooms.[room] with W = false }
let start = rand.Next(height)*width
let finish = rand.Next(height)*width + width - 1
let view = getViewCenteredOn config viewSize (toCharCoord config start)
{
Config = config
Rooms = rooms
RenderedMaze = [||]
VisibleCoords = Set.empty
ViewPos = view
PendingViewPos = view
Start = start
Finish = finish
Player = start
}
|> renderMaze
|> updateVisibleCoords
let private updateView viewSize (game: Game) =
let minDist = 3
let player = toCharCoord game.Config game.Player
let center = lazy (getViewCenteredOn game.Config viewSize player)
let viewDim f =
if f player < f game.ViewPos + minDist - 1 || f player > f game.ViewPos + f viewSize - minDist then f center.Value
else f game.ViewPos
{ game with PendingViewPos = (viewDim fst, viewDim snd) }
let move viewSize dir (game: Game) =
if not game.Won && canMove game game.Player dir then
{ game with Player = nextRoom game.Width game.Player dir }
|> updateVisibleCoords
|> updateView viewSize
else game
let render (viewWidth, viewHeight) game =
let unknown = '░'
let playerChar = '*'
let playerCoord = toCharCoord game.Config game.Player
let viewLeft, viewTop = game.ViewPos
let isVisible coord =
if game.Config.Visibility = Full || game.Won then true
else game.VisibleCoords |> Set.contains coord
let maze =
game.RenderedMaze |> Array.skip viewTop |> Array.truncate viewHeight |> Array.mapi (fun y row ->
row |> Array.skip viewLeft |> Array.truncate viewWidth |> Array.mapi (fun x char ->
let coord = x+viewLeft, y+viewTop
if coord = playerCoord then playerChar
elif isVisible coord then char
else unknown
) |> String
)
let msg =
String.concat " " [
""
if game.Won then
"You Won!!!"
"N for new game."
else
"Use Arrows or H J K L to move."
"Esc for Menu."
]
Seq.append [msg] maze