Mountain Group 105

Check-in Differences

Difference From release-1 To trunk

2022-12-29
21:40
add feature matching exercise Leaf check-in: 0418693d59 user: theo tags: main, trunk
21:05
update supporting materials check-in: ed0e82e855 user: theo tags: main, trunk
2022-12-22
04:42
Remove unnecessary image check-in: 05807086e1 user: theo tags: main, trunk
2022-12-09
04:34
Merge pull request #108 from CSC207-2022F-UofT/readme added m chip issues check-in: aa3fd4bfd4 user: siddharth tags: main, prerelease-41, release-1, trunk
04:19
Update README.md check-in: 05eeb9e756 user: siddharth tags: main, trunk
02:14
Merge pull request #101 from CSC207-2022F-UofT/meta/readme-round-2 Final README Updates Leaf check-in: 127699655f user: theo tags: main, prerelease-40, trunk

Added EXT_LICENSE.


















































































































































































































































































































































































































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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
============================================================
License for src/main/resources/fonts/Strait-Regular.ttf
============================================================

Copyright (c) 2012, Eduardo Tunni (http://www.tipo.net.ar), with Reserved Font Name 'Strait'

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

============================================================
License for src/main/resources/fonts/HomemadeApple-Regular.ttf
============================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

============================================================
License for src/main/resources/fonts/IBMPlexMono-Medium.ttf
============================================================

Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Changes to README.md.

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
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





+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+

-
-
+
-

-
-
+
-

-
-
+
-

-
-
+
-
-
-
-
-
-





-
+





-
+


+
+
+
-
-
+
+
+
+
+
+
+

+
+















-
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
+
+
+
+
+
+
+
+
+



-
+
-
-
+
-
-
-
# Mountain Group 105

![Screenshots of the game](screenshots.webp)

Mountain Group 105 is a rogue-like video game originally created as part of a [software design course](https://artsci.calendar.utoronto.ca/course/csc207h1), with the following premise:

You are a group of four weary travelers.
You have been walking for so long that you no longer have any memory of where you came from or who you are.
You cannot recall anything but the endless action of putting one foot in front of the other, traversing this empty landscape.
Suddenly, you look up, startled out of your reverie.
An imposing mountain looms before you.
Your party steps forward, drawn to it for some inexplicable reason.
You crane your neck to see if you can make out the peak, but the morning mist impedes your view.
You all know you cannot go back to wherever you came from.
You have to keep going.
You have to climb the mountain.
Maybe whatever waits for you up there will remind you of who you are… and what you’re searching for.
> You are a group of four weary travellers.
> You have been walking for so long that you no longer have any memory of where you came from or who you are.
> You cannot recall anything but the endless action of putting one foot in front of the other, traversing this empty landscape.
> Suddenly, you look up, startled out of your reverie.
> An imposing mountain looms before you.
> Your party steps forward, drawn to it for some inexplicable reason.
> You crane your neck to see if you can make out the peak, but the morning mist impedes your view.
> You all know you cannot go back to wherever you came from.
> You have to keep going.
> You have to climb the mountain.
> Maybe whatever waits for you up there will remind you of who you are… and what you’re searching for.

## Quickstart Guide

## Running Instructions
This section contains instructions on how to download and run pre-compiled Jar distribution.

First, this project requires [Java 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html), so make sure that is installed.

### IntelliJ IDEA (IDE) Instructions
Next, navigate to the [latest GitHub release](https://github.com/CSC207-2022F-UofT/course-project-group-105/releases/latest), then download and unzip the attached `course-project-group-105.zip` file.

Finally, navigate to the `bin` folder and run either `course-project-group-105.bat` (on Windows) or `course-project-group-105`.

First download the latest source code from [here](https://vcs.pta.gg/mg105/download) and extract it.
Note: because JavaFX has native components, it is somewhat hit-or-miss whether the pre-compiled Jar will on a specific platform/operating system combination. It is only known to consistently work on Linux+amd64

*If it does not work, try the instructions in the next section*

Next, open the project in IntelliJ by going to to `File > Open`, and navigating to the folder created in the last step.
## Build (and Run) from Source

### IntelliJ IDEA (IDE) Instructions

First download the source code by going to `File > New > Project from Version Control...`, set `Version control:` to `Git`, and the `URL:` to `https://github.com/CSC207-2022F-UofT/course-project-group-105.git`.
Then press `Clone`.

Next, if IntelliJ does not immediately recognize the fact this is a Gradle project (you can tell by the lack of a `Gradle` tab on any of the edges), navigate to the `build.gradle` file in the IDE.
An icon with an elephant should appear, click it.
There should now be a `Gradle` tab on one of the edges.

Finally, open the `Gradle` tab and navigate to `course-project-group-105 > Tasks > application` and double-click run.
Finally, open the `Gradle` tab and navigate to `course-project-group-105 > Tasks > application` and double-click `run`.

### Command-Line (CLI) Instructions

First, this project requires [Java 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html), so make sure that is installed.

Next clone the repository and switch to the directory which can be done by
Next download the source code and change into that directory which can either be done by

```sh
fossil clone https://vcs.pta.gg/mg105
cd mg105
```
git clone https://github.com/CSC207-2022F-UofT/course-project-group-105.git
cd course-project-group-105

or by

```sh
wget https://vcs.pta.gg/mg105/zip/trunk/download.zip
unzip download.zip
cd download
```

if you do not have [Fossil](https://fossil-scm.org) installed.

Finally, this is a standard Gradle project, so to start the application simply run

```sh
gradlew.bat run
```

in the Windows CMD, or

```sh
./gradlew run
```

on any Unix-like operating system (or the built-in IntelliJ IDEA terminal).

## Highlights (and extra hints for the TA)
## Features

- Functionality
  - All twelve (12) of our original user stories are complete.
  - In addition we've added the following extra functionality:
    - Persistence: When a battle ends your characters' stats are automatically saved and are recalled on next game open.
      Even if you lose the game your characters' stats get saved!
    - Minimap: Press 'm' to open a minimap that will show a visual representation of the map you've discovered so far!
  - Keyboard layout:
    - `w`, `a`, `s`, `d`: movement keys, in the usual configuration.
    - `e`: interact with an adjacent chest on the map.
    - `f`: fight an adjacent enemy on the map.
    - `m`: open the minimap (any key closes it).
    - `i`: open/close the inventory.
    - `<SPACEBAR>`: open the walking menu.
    - `k`: open the help text.
    - `t`: start the tutorial.
As a complete game, Mountain Group 105 features

- a randomly generated room-based map,
- the ability to change the character on the map,
- a progressively updating minimap that only shows currently discovered rooms,
- a turn-based battle sequence,
- an inventory system (with health potions and upgrade tokens),
- an item-based character upgrade system,
- an interactive controls display,

and it has been balanced so that it plays decently well!

*Exercise to the reader: match a feature to each of the four screenshots at the top of this README.*

## Program Architecture

Being part of a specific course, this project strictly adheres to [Robert Martin's Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html).

The code is packaged by layers where

- `com.mg105.entities` contain the enterprise business rules,
- `com.mg105.use_cases` contain the application business rules,
- `com.mg105.interface_adapters` contain the interface adapters, and
- `com.mg105.user_interface` and `com.mg105.data_control` both contain frameworks and drivers.

Additionally the code makes use of a plethora of design patterns including

- Code Organization
  - Code is organized by layers, `com.mg105.user_interface`, `com.mg105.interface_adapters`, `com.mg105.data_control`, `com.mg105.use_cases`, `com.mg105.entities`.
- facade pattern in `com.mg105.use_cases.save.Saver` and `com.mg105.use_cases.set_up.data_system_creator.DataStorageSystemCreator`,
  - The `com.mg105.user_interface` package is the only package that knows anything about the graphics library, JavaFX.
  - The `com.mg105.utils` package mostly keeps track of constants.
- Testing
  - As of [96e8a0a3](https://github.com/CSC207-2022F-UofT/course-project-group-105/pull/101/commits/96e8a0a3081fbd400cdc11552415465772a5a1a1), (line) test coverage is as follows:
    - `com.mg105.entities`: 90%
    - `com.mg105.use_cases`: 90%
    - `com.mg105.data_control`: 76%
- simple factory pattern in `com.mg105.use_cases.inventory.ItemFactory` and `com.mg105.user_interface.StandardComponentFactory`,
- builder pattern in `com.mg105.use_cases.set_up.data_system_creator.DataStorageSystemCreator`,
- chain of responsibility pattern in `com.mg105.user_interface.InputListener`, `com.mg105.interface_adapters.InputInterpreter`, and `com.mg105.interface_adapters.InputControllable`,
    - `com.mg105.interface_adapters`: 57%
    - `com.mg105.user_interface`: 1%
    - `com.mg105.utils`: 100%
    - Total: 51%
  - Some tests assume a **completely clean** environment, if some tests fail delete `move_data.csv` and `party_data.csv` and run them again.
- Documentation
  - Current up-to-date Javadoc can be found [here](https://docs.mg105.com/).
- Extra GitHub Features Used
  - GitHub actions to make sure sensitive files (`.idea/*`) are not accidentally modified in a PR ([link](https://github.com/CSC207-2022F-UofT/course-project-group-105/actions/workflows/sanity.yml)).
  - GitHub releases for every merge into `main`, built by GitHub actions ([link](https://github.com/CSC207-2022F-UofT/course-project-group-105/releases)).
  - GitHub pages that host up-to-date Javadoc of `main`, built automatically by GitHub actions ([link](https://docs.mg105.com/)).
- observer pattern in `com.mg105.use_cases.map.RoomUpdater`,
- mediator pattern in `com.mg105.entities.GameState`, and
- dependency injection in essentially all the classes.

## Changes Since Initial Release

As previously mentioned this was originally part of a course project.
The original state of the source code as of the submission date can be found [here](https://vcs.pta.gg/mg105/dir?ci=release-1) or at its original home on GitHub, [here](https://github.com/CSC207-2022F-UofT/course-project-group-105).

Since the original release, the only changes have been a complete UI overhaul (the core functionality and overall architecture is essentially unchanged).
The marking scheme allocated no marks for how pretty it looked, and so no time was spent on that.
In particular the overhaul aims to improve

1. lack of consistent game resolution,
1. unintuitive and inconsistent controls (some buttons, some keyboard), and
1. lack of any real graphics or use of colour.

A full diff of these changes can be found [here](https://vcs.pta.gg/mg105/vdiff?from=release-1&to=trunk).

## Note for Apple Silicon Users (m1 and beyond chips)
Due to some dependency issues this game will NOT build properly for anyone using Apple Silicon (m1 or m2 chips). You
should still be able to make changes and run and create tests.
## Note for Apple Silicon Users

Mountain Group 105 has a hard dependency on JavaFX, which has been flaky on Apple Silicon chips.
The game may not be playable on your hardware.

## Note for Tiling Window Manager Users

Put your window manager into its floating mode before starting the game.
The game expects a constant, self-set resolution, which tiling window managers tend to violate.

## Copyright

Unless mentioned otherwise, code is licensed under the GNU Affero General Public License, Version 3.0.
Unless mentioned in the [EXT_LICENSE](/file?name=EXT_LICENSE&ci=trunk) file, code is licensed under the GNU Affero General Public License, Version 3.0.
See the [LICENSE](/LICENSE) file for more details.

See the [LICENSE](/file?name=LICENSE&ci=trunk) and [EXT_LICENSE](/file?name=EXT_LICENSE&ci=trunk) files for more details.
Here are the exceptions:

- `imgs/background2.jpg` by Fred Seibert is licensed under [CC BY-NC-ND 2.0](https://www.creativecommons.org/licenses/by-nc-nd/2.0/).

Deleted images/closed_issue.png.

cannot compute difference between binary files

Deleted images/closed_project.png.

cannot compute difference between binary files

Deleted images/create_branch.png.

cannot compute difference between binary files

Deleted images/create_pr.png.

cannot compute difference between binary files

Deleted images/create_project.png.

cannot compute difference between binary files

Deleted images/link_branch.png.

cannot compute difference between binary files

Deleted images/link_project.png.

cannot compute difference between binary files

Deleted images/new_issue.png.

cannot compute difference between binary files

Deleted images/new_pr.png.

cannot compute difference between binary files

Deleted images/rename.png.

cannot compute difference between binary files

Deleted images/set_tags.png.

cannot compute difference between binary files

Deleted imgs/background2.jpg.

cannot compute difference between binary files

Deleted project_plan_dev.md.

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








































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
# Project Planning and Development with Github 

In this course project, you are expected to use Github to manage your code. This document describes the workflow for using Github when you are developing the course project. Please read it carefully and follow the instructions. **Try to work through the steps with the help of your team first, but please ask for help if your team gets stuck on any of the steps or needs something clarified.**

## Create a New Github Project
[Github Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) (*Yes, the name of this product is called "Github Projects", do not confuse it with the course project*) is a lightweight project management tool that is integrated to Github. You can use it to track issues, pull requests, visualize tasks status, and track responsibilities. **TAs will mark you project implementation plan and track your progression using the Github project.**

1. Navigate to **Projects** Page on [CSC207 organization page](https://github.com/orgs/CSC207-2022F-UofT/projects)

2. Click **New project**, and click **Create** on the new page.
![](images/create_project.png)

3. Click the title bar to rename the project to your team/project's name, and press **Enter** to save the change.

![](images/rename.png)

4. Link the project to your repository. Navigate to your repository and select **Projects** tab, then click **Add Project** and select the project you just created.

![](images/link_project.png)

5. The project will show up in the the list below.

## Define Your Features for the Implementation Plan
As a part of the project planning, you are required to record all features formulated from your user stories, as **issues** in your Github repository.

1. Navigate to your repository and select **Issues** tab, then click **New issue**.

![](images/new_issue.png)

1. Fill in the title as the name of the feature and provide a brief description of the feature.
**Please use a consistent naming convention for your issues.** For example, you can use the following format: `[Feature x] <feature name>` 

2. On the side bar, select the **Assignee**, **Labels** (Enhancement for your Features), and **Projects**(the one you just created) for the issue. Then click **Submit new issue**.

![](images/set_tags.png)

3. On the project page, you can see an item is automatically created.  :warning: :warning: **Make sure you verify that each feature issue is successfully created in the project.**  :warning: :warning:


## Feature Development
When you work on a feature, you are always required to create a **branch** for the feature and **merge** the branch back to the main branch with **pull requests** when the feature is completed. Note: the below should remind you of the "workflow" we covered in the first lab this term. Please review the details of that document in addition to the below, which provides additional details about how the process works on GitHub. 

1. To create a new branch, navigate to the issue you are assigned to, and click **Create branch** on the right side bar.

![](images/create_branch.png)

2. Select a name and click **Create branch** on the pop-up window. Use the provided command to check out the branch you just created on your local machine.

    *Alternatively, you can create a branch manually, and link it to the issue.*

3. Verify that the branch is successfully linked to the issue.


![](images/link_branch.png)


## Merge Feature Branch to Main Branch
When you finish working on a feature, you are required to merge the feature branch back to the main branch with a **pull request**.

1. After you make changes to the code and commit them to the feature branch, you will see a **Compare & pull request** button on the repository page. Click it to create a pull request.

    *Alternatively, you can create a pull request in the **Pull requests** tab.*

![](images/create_pr.png)

2. Give a meaningful title and description for the pull request, remember please make the name consistent. 
   
   2.1 First make sure that you are merging from the feature branch to the main branch (see blue box).
   
   2.2 Make sure that you set the correct fields as issues (see red box).

![](images/new_pr.png)

3. Select reviewers for the pull request. You can select multiple reviewers. The reviewers will be notified and will review your code. You can also add comments to the pull request. 

4. After the reviewers approve the pull request, you can merge the pull request. :warning: :warning: **Pull requests
must be reviewed and approved by other team members before merging.** :warning: :warning: **Reviewing and approving pull requests will be a part of the evaluation.**

5. After the pull request is merged, the linked issue will be automatically closed. You can verify that the issue is closed by navigating to the issues page and project page.

![](images/closed_issue.png)

![](images/closed_project.png)

6. (Optional) Delete the feature branch after the pull request is merged. You can delete the branch by navigating to the **View all branches** page.

7. (Optional) If the feature is not completed or you want to continue working on the feature, you can reopen the issue and create new pull requests. Remember to change the status of the issue to **In Progress**.


## More Project Management and Other Resoruces (Optional)

- Use issues to keep track of bugs, tasks and other things that need to be done by selecting the appropriate labels.

- Use milestones to group issues into a set of deliverables. To create milestones, navigate to the **Milestones** tab and click **New milestone**.

- Use Projects to tracks issues and collaborate with your team. See the [sample project (Password Manager example)](https://github.com/orgs/CSC207-2022F-UofT/projects/2) for reference.

- Github document for projects: https://docs.github.com/en/issues/planning-and-tracking-with-projects

- Git operations: https://docs.github.com/en/get-started/using-git 

- Git cheat sheet: https://education.github.com/git-cheat-sheet-education.pdf

- I MESSED UP GIT WHAT TO DO?!: https://dangitgit.com/

Added screenshots.webp.

cannot compute difference between binary files

Changes to src/main/java/com/mg105/Application.java.

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
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








-
-
-
+
-
-











+




















-
-
-
-
-
+
-
-




-




-
+
-





+
+
+



-
+







-




-


-
-
+
+
+
+
+



-
-
-
-
-
-
-
-


-
+

-

-




-
+


-
-
-
-


-



-


-
+

-






-

-
+



-
+
+
+

+
+
+

+
+
+
+
+
+
+
+
-
-
+
+


+
+


-
+

+
+


-
+
-
import com.mg105.interface_adapters.battle.BattlePresenter;
import com.mg105.interface_adapters.inventory.InventoryController;
import com.mg105.interface_adapters.inventory.InventoryPresenter;
import com.mg105.interface_adapters.map.MapGeneratorInterpreter;
import com.mg105.interface_adapters.map.MinimapInterpreter;
import com.mg105.interface_adapters.map.RoomInterpreter;
import com.mg105.interface_adapters.map.RoomInterpreterInterface;
import com.mg105.interface_adapters.tutorial.TutorialTextController;
import com.mg105.use_cases.ChestInteractor;
import com.mg105.use_cases.OpponentSetInteractor;
import com.mg105.use_cases.*;
import com.mg105.use_cases.ReplayGenerator;
import com.mg105.use_cases.WalkVisInteractor;
import com.mg105.use_cases.battle.BattleInteractor;
import com.mg105.use_cases.inventory.InventoryInteractor;
import com.mg105.use_cases.map.*;
import com.mg105.use_cases.save.PartySaver;
import com.mg105.use_cases.save.Save;
import com.mg105.use_cases.save.Saver;
import com.mg105.use_cases.set_up.data_system_creator.CreateDataStorage;
import com.mg105.use_cases.set_up.data_system_creator.DataStorageSystemCreator;
import com.mg105.use_cases.set_up.state_setter.GameStateSetter;
import com.mg105.use_cases.set_up.state_setter.PartyCreator;
import com.mg105.user_interface.*;
import com.mg105.utils.UIConstants;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;

import java.awt.*;
import java.util.HashMap;
import java.util.Map;

/**
 * Effectively, the main class that sets up the clean architecture mountain group 105 game!
 */
public class Application extends javafx.application.Application {

    /**
     * Note that while this isn't our main method explicitly, we (probably) need this to effectively be our main method
     * for scoping rules.
     *
     * @param primaryStage the primary stage for this application.
     */
    @Override
    public void start(Stage primaryStage) {

        // Set up the initial use cases
        Inventory inventory = new Inventory();

        GameState state = new GameState(inventory, new WalkingCharacter(new Point(1, 1)));
        GameState state = new GameState(new Inventory(), new WalkingCharacter(new Point(1, 1)));

        // Setting up database
        CreateDataStorage[] databaseCreators = {new MoveDataCreator(), new PartyDataCreator()};
        DataStorageSystemCreator databaseCreator = new DataStorageSystemCreator(databaseCreators);
        databaseCreator.create();

        // Setting the values from the database in game state
        PartyCreator[] partyCreator = {new PartyCreator(new PartyDataAccess(new MoveDataAccess()))};
        GameStateSetter setter = new GameStateSetter(partyCreator);
        setter.setState(state);

        Map<Toggler.ToggleableComponent, Toggleable> drawableComponents = new HashMap<>();
        Map<Toggler.ToggleableComponent, Toggleable> drawableComponents = new HashMap<>(); // We fill this map in later because of the ordering of parameters
        // We fill this map in later because of the ordering of parameters
        SceneController sceneController = new SceneController(
            primaryStage,
            drawableComponents,
            Toggler.ToggleableComponent.MAP
        );

        StandardComponentFactory componentFactory = new StandardComponentFactory(sceneController);

        MapGenerator mapGenerator = new MapGenerator(state);
        MapGeneratorInterpreter mapGeneratorInterpreter = new MapGeneratorInterpreter(mapGenerator);
        MapGeneratorButton generateMapButton = new MapGeneratorButton(mapGeneratorInterpreter, sceneController);
        MainMenu mainMenu = new MainMenu(generateMapButton);
        MainMenu mainMenu = new MainMenu(sceneController, generateMapButton, componentFactory);

        RoomGetterInterface roomGetter = new RoomGetter(state);
        RoomInterpreterInterface roomInterpreter = new RoomInterpreter(roomGetter);
        MapDrawer mapDrawer = new MapDrawer(roomInterpreter);
        drawableComponents.put(Toggler.ToggleableComponent.MAIN_MENU, mainMenu);
        drawableComponents.put(Toggler.ToggleableComponent.MAP, mapDrawer);

        // Minimap setup
        MinimapInterpreter minimapInterpreter = new MinimapInterpreter(roomGetter);
        MinimapDrawer minimapDrawer = new MinimapDrawer(minimapInterpreter);
        drawableComponents.put(Toggler.ToggleableComponent.MINIMAP, minimapDrawer);

        // InventoryDisplay set up
        InventoryPresenter inventoryPresenter = new InventoryPresenter();
        InventoryInteractor inventoryInteractor = new InventoryInteractor(state, inventoryPresenter);
        InventoryDisplay inventoryDisplay = new InventoryDisplay(new InventoryController(
            inventoryInteractor));
        InventoryDisplay inventoryDisplay = new InventoryDisplay(
            new InventoryController(inventoryInteractor),
            componentFactory,
            new PartyGetter(new PartyStatGetter(state))
        );
        inventoryPresenter.setView(inventoryDisplay);
        drawableComponents.put(Toggler.ToggleableComponent.INVENTORY, inventoryDisplay);

        /////Tutorial scene////
        TutorialTextController textChanger = new TutorialTextController(false);
        TutorialTextDisplay textDisplay = new TutorialTextDisplay();
        TutorialTextWindow tutorialDisplay = new TutorialTextWindow(textChanger, textDisplay);
        drawableComponents.put(Toggler.ToggleableComponent.TUTORIAL, tutorialDisplay);
        //////////////////////

        //WalkingMenu scene//
        WalkVisInteractor walkVisInteractor = new WalkVisInteractor(state);
        WalkVisController walkVisController = new WalkVisController(walkVisInteractor);
        WalkingMenu walkingMenu = new WalkingMenu(walkVisController);
        WalkingMenu walkingMenu = new WalkingMenu(walkVisController, sceneController, componentFactory);
        drawableComponents.put(Toggler.ToggleableComponent.WALK_MENU, walkingMenu);
        /////////////////////

        //LoseMenu scene//
        ReplayGenerator replayGenerator = new ReplayGenerator(state, minimapInterpreter);
        replayGenerator.replay();
        ReplayGeneratorInterpreter replayGeneratorInterpreter = new ReplayGeneratorInterpreter(replayGenerator);
        ReplayGeneratorButton loseButton = new ReplayGeneratorButton(replayGeneratorInterpreter, sceneController, Toggler.ToggleableComponent.LOSE_MENU);
        LoseMenu loseMenu = new LoseMenu(loseButton);
        LoseMenu loseMenu = new LoseMenu(loseButton, componentFactory, mainMenu);
        drawableComponents.put(Toggler.ToggleableComponent.LOSE_MENU, loseMenu);

        ////////////////////

        //BattleMenu scene//
        //OpponentSet setup
        OpponentSetInteractor opponentInteractor = new OpponentSetInteractor(state);

        // Creating Saver
        Save[] savers = {new PartySaver(state, new PartyDataAccess(new MoveDataAccess()))};
        Saver saver = new Saver(savers);

        //Battle setup
        BattleInteractor battleInteractor = new BattleInteractor(state, inventoryInteractor, saver);
        BattlePresenter battlePresenter = new BattlePresenter(battleInteractor, sceneController);
        BattleMenu battleMenu = new BattleMenu(battlePresenter);
        BattleMenu battleMenu = new BattleMenu(battlePresenter, componentFactory);
        drawableComponents.put(Toggler.ToggleableComponent.BATTLE, battleMenu);
        /////////////////////

        RoomUpdater roomUpdater = new RoomUpdater();
        roomUpdater.addObserver(mapDrawer);
        roomUpdater.addObserver(minimapInterpreter);
        CharacterMoverInterface characterMover = new CharacterMover(state, roomUpdater);

        /////WinGame Scene/////
        ReplayGeneratorButton winButton = new ReplayGeneratorButton(replayGeneratorInterpreter, sceneController, Toggler.ToggleableComponent.WIN_MENU);
        WinMenu winMenu = new WinMenu(winButton);
        WinMenu winMenu = new WinMenu(winButton, componentFactory, mainMenu);
        WinDisplay winDisplay = new WinDisplay(sceneController, roomGetter, replayGenerator);
        roomUpdater.addObserver(winDisplay);
        drawableComponents.put(Toggler.ToggleableComponent.WIN_MENU, winMenu);
        /////////////////

        KeymapDisplay keymapDisplay = new KeymapDisplay(componentFactory);
        drawableComponents.put(Toggler.ToggleableComponent.CONTROLS, keymapDisplay);

        IntroDisplay introDisplay = new IntroDisplay(sceneController, keymapDisplay, componentFactory);
        drawableComponents.put(Toggler.ToggleableComponent.INTRO, introDisplay);

        ChestInteractor chestInteractor = new ChestInteractor(state, inventoryInteractor, roomUpdater);

        Map<Toggler.ToggleableComponent, InputControllable> inputPassthroughs = new HashMap<>();
        inputPassthroughs.put(Toggler.ToggleableComponent.MAIN_MENU, mainMenu);
        inputPassthroughs.put(Toggler.ToggleableComponent.WIN_MENU, winMenu);
        inputPassthroughs.put(Toggler.ToggleableComponent.LOSE_MENU, loseMenu);
        inputPassthroughs.put(Toggler.ToggleableComponent.INTRO, introDisplay);
        inputPassthroughs.put(Toggler.ToggleableComponent.BATTLE, battleMenu);

        InputInterpreter inputInterpreter = new InputInterpreter(characterMover, sceneController, textChanger, chestInteractor,
            opponentInteractor);
        InputInterpreter inputInterpreter = new InputInterpreter(characterMover, sceneController, chestInteractor,
            opponentInteractor, inventoryDisplay, walkingMenu, sceneController, inputPassthroughs);
        InputListener inputListener = new InputListener(inputInterpreter);

        sceneController.toggle(Toggler.ToggleableComponent.INTRO);
        sceneController.toggle(Toggler.ToggleableComponent.MAIN_MENU);

        primaryStage.addEventFilter(KeyEvent.KEY_TYPED, inputListener);
        sceneController.toggle(Toggler.ToggleableComponent.MAIN_MENU);
        primaryStage.addEventFilter(KeyEvent.ANY, keymapDisplay);
        primaryStage.setResizable(false);
        primaryStage.setMinWidth(UIConstants.CANVAS_SIZE);
        primaryStage.setMinHeight(UIConstants.CANVAS_SIZE);
        primaryStage.show();
    }

}
}

Deleted src/main/java/com/mg105/entities/GiveTutorial.java.

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


































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.entities;


import com.mg105.utils.TutorialTexts;

/**
 * Data for what actions player has completed, contains methods for mutating the data
 */
public class GiveTutorial {

    private boolean moved;
    private boolean attacked;
    private boolean usedItem;

    /**
     * Constructor for GiveTutorial entity
     *
     * @param moved    whether player has moved
     * @param attacked whether player has attacked
     * @param usedItem whether player has opened a chest
     */
    public GiveTutorial(boolean moved, boolean attacked, boolean usedItem) {
        this.moved = moved;
        this.attacked = attacked;
        this.usedItem = usedItem;
    }

    /**
     * Set moved, attacked, usedItem to true if they have been performed by player
     * <p>
     * action should be a valid action
     *
     * @param action the action that has been performed
     */
    public void actionPerformedSetter(String action) {
        if (action.equalsIgnoreCase(TutorialTexts.MOVED)) {
            this.moved = true;
        } else if (action.equalsIgnoreCase(TutorialTexts.ATTACKED)) {
            this.attacked = true;
        } else if (action.equalsIgnoreCase(TutorialTexts.USED_ITEM)) {
            this.usedItem = true;
        }

    }

    /**
     * Get if player has moved, attacked, and usedItem.
     * <p>
     * action should be a valid action
     *
     * @param action the action that is checked
     * @return whether the player has performed each action
     */
    public boolean actionPerformedGetter(String action) {
        if (action.equalsIgnoreCase(TutorialTexts.MOVED)) {
            return this.moved;
        } else if (action.equalsIgnoreCase(TutorialTexts.ATTACKED)) {
            return this.attacked;
        } else if (action.equalsIgnoreCase(TutorialTexts.USED_ITEM)) {
            return this.usedItem;
        } else {
            return false;
        }
    }

}

Added src/main/java/com/mg105/interface_adapters/InputControllable.java.














1
2
3
4
5
6
7
8
9
10
11
12
13
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.interface_adapters;

/**
 * InputControllable represents something that can be directly controlled by a keyboard input.
 */
public interface InputControllable {
    /**
     * Send key to this component.
     *
     * @param key the key this component should process.
     */
    void interpret(String key);
}

Changes to src/main/java/com/mg105/interface_adapters/InputInterpreter.java.

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
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


-



-



+








+
-
-
-
+
+
+
+






-


+
+
+
+

-
-
-
+
+
+
+
+
+
+
+


-


+
+
+
+








+
+
+
+
+
+
-
+
+
+


-
-
+
-
-
-
-
+
-
-
-
-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-

-
-
-
-
+
-



-





-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+


-
-
-
-
+
+
+
+
+
+
+
+



-


package com.mg105.interface_adapters;

import com.mg105.interface_adapters.tutorial.TutorialTextController;
import com.mg105.use_cases.ChestInteractor;
import com.mg105.use_cases.OpponentSetInteractor;
import com.mg105.use_cases.map.CharacterMoverInterface;
import com.mg105.utils.TutorialTexts;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.util.Map;

/**
 * InputInterpreter takes in keyboard inputs and distributes them to their appropriate use cases.
 */
public class InputInterpreter {
    private final @NotNull CharacterMoverInterface mover;
    private final @NotNull Toggler toggler;
    private final @NotNull ChestInteractor chestInteractor;
    private final @NotNull OpponentSetInteractor opponentInteractor;
    private final @NotNull TutorialTextController textChanger;

    private final @NotNull OpponentSetInteractor opponentInteractor;
    private final @NotNull InputControllable inventory;
    private final @NotNull InputControllable walkMenu;
    private final @NotNull InputControllable alerter;
    private final @NotNull Map<Toggler.ToggleableComponent, InputControllable> passthroughs;

    /**
     * Create a new InputInterpreter that translates keyboard inputs to appropriate function invocations.
     *
     * @param mover              the character mover.
     * @param toggler            the toggler used to change the displayed interface.
     * @param textChanger        the text controller for tutorial
     * @param chestInteractor    the ChestInteractor used to interact with chests.
     * @param opponentInteractor the interactor used to interact with opponents.
     * @param inventory          the inventory.
     * @param walkMenu           the walking menu.
     * @param alerter            the alerter.
     * @param passthroughs       components that just get input directly pushed to them.
     */
    public InputInterpreter(@NotNull CharacterMoverInterface mover, @NotNull Toggler toggler,
                            @NotNull TutorialTextController textChanger, @NotNull ChestInteractor chestInteractor,
                            @NotNull OpponentSetInteractor opponentInteractor) {
    public InputInterpreter(@NotNull CharacterMoverInterface mover,
                            @NotNull Toggler toggler,
                            @NotNull ChestInteractor chestInteractor,
                            @NotNull OpponentSetInteractor opponentInteractor,
                            @NotNull InputControllable inventory,
                            @NotNull InputControllable walkMenu,
                            @NotNull InputControllable alerter,
                            @NotNull Map<Toggler.ToggleableComponent, InputControllable> passthroughs) {
        this.mover = mover;
        this.toggler = toggler;
        this.textChanger = textChanger;
        this.opponentInteractor = opponentInteractor;
        this.chestInteractor = chestInteractor;
        this.inventory = inventory;
        this.walkMenu = walkMenu;
        this.alerter = alerter;
        this.passthroughs = passthroughs;
    }

    /**
     * Interpret key being pressed as an action.
     *
     * @param key the key being pressed as a string.
     */
    public void interpret(String key) {
        // There is one ALWAYS GLOBAL keyboard mapping... 'k' to show the keyboard mappings.
        if (key.equals("k")) {
            toggler.toggle(Toggler.ToggleableComponent.CONTROLS);
            return;
        }

        switch (toggler.getCurrentComponent()) {
        Toggler.ToggleableComponent currentComponent = toggler.getCurrentComponent();

        switch (currentComponent) {
            case MAP -> {
                switch (key) {
                    case "w" -> {
                        mover.generateMapMoveBy(new Point(0, -1));
                    case "w" -> mover.generateMapMoveBy(new Point(0, -1));
                        textChanger.getTutorial().setActionPerformed(TutorialTexts.MOVED);
                    }
                    case "a" -> {
                        mover.generateMapMoveBy(new Point(-1, 0));
                    case "a" -> mover.generateMapMoveBy(new Point(-1, 0));
                        textChanger.getTutorial().setActionPerformed(TutorialTexts.MOVED);
                    }
                    case "s" -> {
                        mover.generateMapMoveBy(new Point(0, 1));
                    case "s" -> mover.generateMapMoveBy(new Point(0, 1));
                        textChanger.getTutorial().setActionPerformed(TutorialTexts.MOVED);
                    }
                    case "d" -> {
                        mover.generateMapMoveBy(new Point(1, 0));
                    case "d" -> mover.generateMapMoveBy(new Point(1, 0));
                        textChanger.getTutorial().setActionPerformed(TutorialTexts.MOVED);
                    }

                    case "k" -> {
                        toggler.toggle(Toggler.ToggleableComponent.TUTORIAL);
                        textChanger.setShowControls(true);

                    }
                    case "t" -> {
                        toggler.toggle(Toggler.ToggleableComponent.TUTORIAL);
                        textChanger.setChangeText();
                    }
                    case "e" -> {
                        chestInteractor.getChestItem();
                    case "e" -> chestInteractor.getChestItem();
                        // tutorial only cares that you pick up item, not that you use it
                        textChanger.getTutorial().setActionPerformed(TutorialTexts.USED_ITEM);
                    }
                    case "i" -> toggler.toggle(Toggler.ToggleableComponent.INVENTORY);
                    case " " ->
                        //There is a warning if curly brackets are used on this block.
                        // I don't know what is correct to do in this situation.
                        toggler.toggle(Toggler.ToggleableComponent.WALK_MENU);
                    case "c" -> toggler.toggle(Toggler.ToggleableComponent.WALK_MENU);

                    case "f" -> {
                        if (opponentInteractor.setOpponentSet()) {
                            toggler.toggle(Toggler.ToggleableComponent.BATTLE);
                            textChanger.getTutorial().setActionPerformed(TutorialTexts.ATTACKED);
                        }
                    }
                    case "m" -> toggler.toggle(Toggler.ToggleableComponent.MINIMAP);
                }
            }
            case TUTORIAL -> {
                switch (key) {
            case WALK_MENU -> {
                if (key.equals("c")) {
                    case "w", "a", "s", "d" -> {
                        toggler.toggle(Toggler.ToggleableComponent.TUTORIAL);
                        textChanger.setChangeText();
                    }
                    toggler.toggle(Toggler.ToggleableComponent.WALK_MENU);
                } else {
                    walkMenu.interpret(key);
                }
                    case "k" -> textChanger.setShowControls(true);
                }
            }

            case WALK_MENU -> {
                if (key.equals(" ")) {
                    toggler.toggle(Toggler.ToggleableComponent.WALK_MENU);
            }
            case MINIMAP -> {
                if (key.equals("m")) {
                    toggler.toggle(Toggler.ToggleableComponent.MINIMAP);
                }
            }
            case INVENTORY -> {
                if (key.equals("i")) {
                    toggler.toggle(Toggler.ToggleableComponent.INVENTORY);
                } else {
                    inventory.interpret(key);
                }
            }
            case MINIMAP -> toggler.toggle(Toggler.ToggleableComponent.MINIMAP);
            case INVENTORY -> {
                if (key.equals("i")) {
                    toggler.toggle(Toggler.ToggleableComponent.INVENTORY);
            case ALERT -> {
                if (key.equals(" ")) {
                    alerter.interpret(key);
                }
            }
            default -> {
                if (passthroughs.containsKey(currentComponent)) {
                    passthroughs.get(currentComponent).interpret(key);
                }
            }
        }

    }
}

Added src/main/java/com/mg105/interface_adapters/PartyGetter.java.

































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.interface_adapters;

import com.mg105.use_cases.PartyStatGetter;
import org.jetbrains.annotations.NotNull;

/**
 * Get information about a party member.
 */
public class PartyGetter {
    private final @NotNull PartyStatGetter getter;

    /**
     * Create a new PartyGetter.
     *
     * @param getter the getter used to access information.
     */
    public PartyGetter(@NotNull PartyStatGetter getter) {
        this.getter = getter;
    }

    /**
     * Get if name is fainted.
     *
     * @param name the party member's name.
     *
     * @return if name is fainted.
     */
    public boolean isFainted(@NotNull String name) {
        return getter.isFainted(name);
    }

    /**
     * Get the HP of a party member.
     *
     * @param name the party member's name.
     *
     * @return the HP of a party member.
     */
    public int getHp(@NotNull String name) {
        return getter.getHp(name);
    }

    /**
     * Get the maximum HP of a party member.
     *
     * @param name the party member's name.
     *
     * @return the maximum HP of a party member.
     */
    public int getMaxHp(@NotNull String name) {
        return getter.getMaxHp(name);
    }

    /**
     * Get the Dmg of a party member.
     *
     * @param name the party member's name.
     *
     * @return the Dmg of a party member.
     */
    public int getDmg(@NotNull String name) {
        return getter.getDmg(name);
    }
}

Changes to src/main/java/com/mg105/interface_adapters/Toggler.java.

20
21
22
23
24
25
26






27
28
29
30
31
32
33
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39







+
+
+
+
+
+







     */
    @NotNull ToggleableComponent getCurrentComponent();

    /**
     * All the possible components that could theoretically be toggled.
     */
    enum ToggleableComponent {
        /**
         * An alert is visible.
         * <p>
         * Note: in practice this one should not be directly toggled.
         */
        ALERT,
        /**
         * The main menu
         */
        MAIN_MENU,
        /**
         * The main map
         */
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
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







-
-
-
-










-
-
+
+
+
+
+
+
+
+
+


         */
        INVENTORY,
        /**
         * The battle menu
         */
        BATTLE,
        /**
         * The tutorial window
         */
        TUTORIAL,
        /**
         * The character selection screen.
         */
        WALK_MENU,
        /**
         * The game Lose menu
         */
        LOSE_MENU,
        /**
         * The game Win menu
         */
        WIN_MENU

        WIN_MENU,
        /**
         * The intro message
         */
        INTRO,
        /**
         * The keyboard layout
         */
        CONTROLS
    }
}

Changes to src/main/java/com/mg105/interface_adapters/battle/BattlePresenter.java.

110
111
112
113
114
115
116







117
118
119
120
121
122
123
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130







+
+
+
+
+
+
+







     * returns null iff the battle has ended
     *
     * @return a String of the name of the moving character
     */
    public String roundStart() {
        return interactor.roundStart();
    }

    /**
     * Have the opponents apply an attack, if necessary.
     */
    public void applyOpponentAttack() {
        interactor.applyOpponentAttack();
    }

    /**
     * Returns the name of every BattleCharacter which can be targeted by the given move.
     * Note: Function should only be called from view when caster is friendly, so method does not accommodate for case
     * where caster is an opponent.
     *
     * @param moveNum    integer representing which of the two Moves is being used.

Changes to src/main/java/com/mg105/interface_adapters/inventory/InventoryPresenter.java.

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
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







-
+




















-
+




-
+







     * @param itemDetails  the state of the item that was potentially used
     * @see ItemDetails
     * @see InventoryViewInterface
     */
    @Override
    public void addItem(boolean isSuccessful, ItemDetails itemDetails) {
        if (isSuccessful) {
            this.display.alert("Successfully added a " + itemDetails.getName() + ".");
            this.display.alert("Successfully added a(n) " + itemDetails.getName() + ".");
            return;
        }

        this.display.alert(itemDetails.getName() +
            " could not be added. The inventory might be full, try removing an item.");
    }

    /**
     * Sends an alert the view about the status of the potentially removed item.
     * Also updates the view to display the appropriate amount of items left fo that type
     *
     * @param isSuccessful if an item of itemName was removed from the inventory
     * @param itemDetails  the state of the item that was potentially used
     * @see ItemDetails
     * @see InventoryViewInterface
     */
    @Override
    public void removeItem(boolean isSuccessful, ItemDetails itemDetails) {

        if (!isSuccessful) {
            this.display.alert("Could not remove a " + itemDetails.getDescription() + ".");
            this.display.alert("Could not remove a(n) " + itemDetails.getDescription() + ".");
            return;

        }

        this.display.alert("Successfully removed a " + itemDetails.getName() + ".");
        this.display.alert("Successfully removed a(n) " + itemDetails.getName() + ".");

        if (itemDetails.getCount() == 0) {

            this.display.removeItemView(itemDetails.getName());
            return;

        }

Changes to src/main/java/com/mg105/interface_adapters/map/RoomInterpreter.java.

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
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







-
-
-
+
+
+
-
-
-
-
+
-
-

-
-
-
+
+
+
+
+
+
+

-
-
+

-
-




-

-
+
+
+
+




-
+



-
+



-
+


-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


     * Get the current room as represented in an easier to draw grid.
     *
     * @return the room as a 2 dimension array of TileType representing the current state of the room. Note that (0, 0)
     * represents the top-left corner and (MapConstants.ROOM_SIZE, MapConstants.ROOM_SIZE) represents the bottom
     * right corner.
     */
    @Override
    public RoomTileType[][] getCurrentRoom() {
        RoomTileType[][] canvas = new RoomTileType[MapConstants.ROOM_SIZE][MapConstants.ROOM_SIZE];

    public @NotNull RoomState getCurrentRoom() {
        RoomTileType[][] baseLayer = new RoomTileType[MapConstants.ROOM_SIZE + 1][MapConstants.ROOM_SIZE];
        RoomTileType[][] offsetLayer = new RoomTileType[MapConstants.ROOM_SIZE + 1][MapConstants.ROOM_SIZE];
        for (int y = 1; y < MapConstants.ROOM_SIZE - 1; y++) {
            for (int x = 1; x < MapConstants.ROOM_SIZE - 1; x++) {
                canvas[y][x] = RoomTileType.FLOOR;
            }

        }

        for (int i = 0; i < MapConstants.ROOM_SIZE; i++) {
            canvas[i][MapConstants.ROOM_SIZE - 1] = RoomTileType.WALL;
            canvas[i][0] = RoomTileType.WALL;
        }
            offsetLayer[i][MapConstants.ROOM_SIZE - 1] = RoomTileType.WALL;
            offsetLayer[i][0] = RoomTileType.WALL;
            offsetLayer[0][i] = RoomTileType.WALL;
            offsetLayer[MapConstants.ROOM_SIZE - 1][i] = RoomTileType.WALL;

            offsetLayer[MapConstants.ROOM_SIZE][i] = RoomTileType.WALL_FACE;
        }
        for (int i = 1; i < MapConstants.ROOM_SIZE - 1; i++) {
            canvas[MapConstants.ROOM_SIZE - 1][i] = RoomTileType.WALL_WITH_FACE;
            canvas[0][i] = RoomTileType.WALL_WITH_FACE;
            offsetLayer[1][i] = RoomTileType.WALL_FACE;
        }
        canvas[MapConstants.ROOM_SIZE - 1][0] = RoomTileType.WALL_WITH_FACE;
        canvas[MapConstants.ROOM_SIZE - 1][MapConstants.ROOM_SIZE - 1] = RoomTileType.WALL_WITH_FACE;

        RoomLayout room = getter.getCurrentRoomLayout();

        for (Point doorway : room.getDoorways()) {
            canvas[doorway.y][doorway.x] = RoomTileType.EXIT;
            if (doorway.x == 0 || doorway.x == MapConstants.ROOM_SIZE - 1) {
                canvas[doorway.y - 1][doorway.x] = RoomTileType.WALL_WITH_FACE;
                offsetLayer[doorway.y][doorway.x] = RoomTileType.WALL_FACE;
            } else {
                offsetLayer[doorway.y][doorway.x] = null;
                offsetLayer[doorway.y + 1][doorway.x] = null;
            }
        }

        for (Point chest : room.getClosedChests()) {
            canvas[chest.y][chest.x] = RoomTileType.CHEST;
            baseLayer[chest.y][chest.x] = RoomTileType.CHEST;
        }

        for (Point chest : room.getOpenChests()) {
            canvas[chest.y][chest.x] = RoomTileType.CHEST_OPEN;
            baseLayer[chest.y][chest.x] = RoomTileType.CHEST_OPEN;
        }

        for (Point opponents : room.getOpponents()) {
            canvas[opponents.y][opponents.x] = RoomTileType.OPPONENT_SET;
            baseLayer[opponents.y][opponents.x] = RoomTileType.OPPONENT_SET;
        }

        return canvas;
    }

        return new RoomState(baseLayer, offsetLayer, room.getPlayer(), getter.getWalkingSprite());
    /**
     * Get the current player position in the room.
     *
     * @return the current player position in the room.
     */
    @Override
    public @NotNull Point getPlayer() {
        return getter.getCurrentRoomLayout().getPlayer();
    }

    /**
     * Retrieves the sprite String currently associated with the WalkingCharacter.
     *
     * @return a file name/location as a String for the desired character sprite.
     */
    @Override
    public @NotNull String getCharacterSprite() {
        return this.getter.getWalkingSprite();
    }
}

Changes to src/main/java/com/mg105/interface_adapters/map/RoomInterpreterInterface.java.

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
1
2
3
4


5
6
7
8
9

10
11
12
13

14














15




-
-





-
+



-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-

package com.mg105.interface_adapters.map;

import org.jetbrains.annotations.NotNull;

import java.awt.*;

/**
 * Layout the current room.
 */
public interface RoomInterpreterInterface {
    /**
     * Lay out the current room as a square of tiles to be displayed.
     * Get the current room state.
     *
     * @return the state of the current room.
     */
    RoomTileType[][] getCurrentRoom();
    @NotNull RoomState getCurrentRoom();

    /**
     * Get the position of the player within the room.
     *
     * @return the position of the player within the room.
     */
    @NotNull Point getPlayer();

    /**
     * Get the path of the current character sprite.
     *
     * @return the path of the current character sprite
     */
    @NotNull String getCharacterSprite();
}

Added src/main/java/com/mg105/interface_adapters/map/RoomState.java.










































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.interface_adapters.map;

import org.jetbrains.annotations.NotNull;

import java.awt.*;

/**
 * RoomState is a easy-to-draw representation of the state of the current room.
 */
public class RoomState {
    private final @NotNull RoomTileType[][] baseLayer;
    private final @NotNull RoomTileType[][] offsetLayer;
    private final @NotNull Point playerPosition;
    private final @NotNull String playerSprite;

    /**
     * Create a new RoomState.
     *
     * @param baseLayer      the base layer (chests, battles)
     * @param offsetLayer    the wall and decoration layer (walls, wall faces)
     * @param playerPosition the position of the player in the room.
     * @param playerSprite   the player sprite.
     */
    public RoomState(@NotNull RoomTileType[][] baseLayer,
                     @NotNull RoomTileType[][] offsetLayer,
                     @NotNull Point playerPosition,
                     @NotNull String playerSprite) {
        this.baseLayer = baseLayer;
        this.offsetLayer = offsetLayer;
        this.playerPosition = playerPosition;
        this.playerSprite = playerSprite;
    }

    /**
     * Get the representation of the contents of the room.
     * <p>
     * Note the return value is of size MapConstants.MAP_SIZE x MapConstants.MAP_SIZE+1.
     *
     * @return a 2D representation of the contents of the room.
     */
    public @NotNull RoomTileType[][] getBaseLayer() {
        return baseLayer;
    }

    /**
     * Get a representation of the border of the room
     * <p>
     * Note the return value is of size MapConstants.MAP_SIZE x MapConstants.MAP_SIZE+1.
     *
     * @return a representation of the border of the room.
     */
    public @NotNull RoomTileType[][] getOffsetLayer() {
        return offsetLayer;
    }

    /**
     * Get the player position in the room.
     *
     * @return the player position in the room.
     */
    public @NotNull Point getPlayerPosition() {
        return playerPosition;
    }

    /**
     * Get the player sprite.
     *
     * @return the player sprite.
     */
    public @NotNull String getPlayerSprite() {
        return playerSprite;
    }
}

Changes to src/main/java/com/mg105/interface_adapters/map/RoomTileType.java.

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
1
2
3
4
5
6
7




8
9
10
11

12
13

14




15
16
17
18
19
20
21







-
-
-
-




-
+

-
+
-
-
-
-







package com.mg105.interface_adapters.map;

/**
 * Types of tiles that can appear within a room.
 */
public enum RoomTileType {
    /**
     * Empty floor that can be walked on
     */
    FLOOR,
    /**
     * Wall that cannot be walked on.  Mainly used for the border
     */
    WALL,
    /**
     * A wall where you can see the side
     * The isometric wall face.
     */
    WALL_WITH_FACE,
    WALL_FACE,
    /**
     * A doorway
     */
    EXIT,
    /**
     * A treasure chest
     */
    CHEST,
    /**
     * A treasure chest that has been opened
     */

Deleted src/main/java/com/mg105/interface_adapters/tutorial/TutorialTextController.java.

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


















































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.interface_adapters.tutorial;

import com.mg105.use_cases.PlayerGetsTutorial;
import com.mg105.utils.TutorialTexts;

import java.util.List;

/**
 * A controller that converts phase number into displayed text
 */
public class TutorialTextController {

    private final PlayerGetsTutorial tutorial = new PlayerGetsTutorial(TutorialTexts.PHASES, 0);
    private boolean changeText;
    private boolean showControls = false;

    /**
     * A constructor for the tutorial controller
     *
     * @param changeText whether to initially change the text.
     */
    public TutorialTextController(boolean changeText) {
        this.changeText = changeText;
    }

    /**
     * Gets the current text that should be displayed
     *
     * @return what the text displayed at the bottom of the screen should be
     */
    public String bottomText() {
        return tutorial.allPhases().get(tutorial.currentPhase());
    }

    /**
     * Go to the next tutorial phase
     */
    public void nextPhase() {
        this.tutorial.nextPhase();
    }

    /**
     * Make text start changing
     */
    public void setChangeText() {
        this.changeText = !this.changeText;
    }

    /**
     * Check if tutorial phases should advance
     *
     * @return if text should start changing
     */
    public boolean changeText() {
        return this.changeText;
    }

    /**
     * Check if player should be shown controls again
     *
     * @return whether player should be shown the control texts
     */
    public boolean getShowControls() {
        return this.showControls;
    }

    /**
     * Tell player the controls again
     *
     * @param show the text on the screen when true
     */
    public void setShowControls(boolean show) {
        this.showControls = show;
    }

    /**
     * Get an instance of the PlayerGetsTutorial use case
     *
     * @return the tutorial instance
     */
    public PlayerGetsTutorial getTutorial() {
        return this.tutorial;
    }

    /**
     * Get if the tutorial is complete, changes text if it is
     *
     * @return if the tutorial is complete
     */
    public boolean isComplete() {
        return tutorial.isComplete();
    }

    /**
     * Returns if the action has been performed
     *
     * @param action that is checked
     * @return if the specified action has been performed
     */
    public boolean getActionPerformed(String action) {
        return !tutorial.getActionPerformed(action);
    }

    /**
     * Get names of all phases of tutorial
     *
     * @return the list of all tutorial phases
     */
    public List<String> allPhases() {
        return tutorial.allPhases();
    }

}

Added src/main/java/com/mg105/use_cases/PartyStatGetter.java.

































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.use_cases;

import com.mg105.entities.GameState;
import org.jetbrains.annotations.NotNull;

/**
 * Get ad hoc information about party members.
 */
public class PartyStatGetter {
    private final @NotNull GameState state;

    /**
     * Create a new PartyStatGetter.
     *
     * @param state the game state that stores the data.
     */
    public PartyStatGetter(@NotNull GameState state) {
        this.state = state;
    }

    /**
     * Get if name is fainted.
     *
     * @param name the party member's name.
     *
     * @return if name is fainted.
     */
    public boolean isFainted(@NotNull String name) {
        return state.getFaintedPartyMember(name) != null;
    }

    /**
     * Get the HP of a party member.
     *
     * @param name the party member's name.
     *
     * @return the HP of a party member.
     */
    public int getHp(@NotNull String name) {
        return state.getPartyMember(name).getHp();
    }

    /**
     * Get the maximum HP of a party member.
     *
     * @param name the party member's name.
     *
     * @return the maximum HP of a party member.
     */
    public int getMaxHp(@NotNull String name) {
        return state.getPartyMember(name).getMaxHp();
    }

    /**
     * Get the Dmg of a party member.
     *
     * @param name the party member's name.
     *
     * @return the Dmg of a party member.
     */
    public int getDmg(@NotNull String name) {
        return state.getPartyMember(name).getDmg();
    }
}

Deleted src/main/java/com/mg105/use_cases/PlayerGetsTutorial.java.

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





















































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.use_cases;

import com.mg105.entities.GiveTutorial;
import com.mg105.utils.TutorialTexts;

import java.util.List;

/**
 * Class for determining what phase of the tutorial the player is on, and changing the phase
 */
public class PlayerGetsTutorial {
    private final List<String> tutorialPhases;  // Go through multiple phases of tutorial in order
    private final GiveTutorial tutorial;
    private int currentPhase;


    /**
     * Constructor for PlayerGetsTutorial use case
     *
     * @param tutorialPhases a list of all possible phases in the tutorial
     * @param currentPhase   the integer representing what phase the player is on in the tutorial
     */
    public PlayerGetsTutorial(List<String> tutorialPhases, int currentPhase) {
        this.tutorialPhases = tutorialPhases;
        this.currentPhase = currentPhase;
        this.tutorial = new GiveTutorial(false, false, false);
    }

    /**
     * Check if all required actions performed by player
     *
     * @return whether all actions are complete
     */
    public boolean isComplete() {
        return tutorial.actionPerformedGetter(TutorialTexts.MOVED) & tutorial.actionPerformedGetter(TutorialTexts.ATTACKED) & tutorial.actionPerformedGetter(TutorialTexts.USED_ITEM);
    }

    /**
     * Get names of all phases of tutorial
     *
     * @return the list of all tutorial phases
     */
    public List<String> allPhases() {
        return this.tutorialPhases;
    }

    /**
     * Get current phase of tutorial, which is first index of phase list
     *
     * @return the current phase of tutorial
     */
    public int currentPhase() {
        return this.currentPhase;
    }

    /**
     * Advance current phase by 1
     */
    public void nextPhase() {
        if (currentPhase < TutorialTexts.PHASES.size() - 1) {
            this.currentPhase++;
        }
    }

    /**
     * Set the action to true if it has been performed
     *
     * @param action to set to performed
     */
    public void setActionPerformed(String action) {
        this.tutorial.actionPerformedSetter(action);
    }

    /**
     * Check if specific action has been performed
     *
     * @param action get if it has been performed yet
     * @return if the action has been performed
     */
    public boolean getActionPerformed(String action) {
        return this.tutorial.actionPerformedGetter(action);
    }

}

Changes to src/main/java/com/mg105/use_cases/ReplayGenerator.java.

1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12





-







package com.mg105.use_cases;

import com.mg105.entities.BattleCharacter;
import com.mg105.entities.GameState;
import com.mg105.entities.items.MegaPotion;
import com.mg105.use_cases.map.MapGenerator;
import com.mg105.utils.PartyConstants;
import org.jetbrains.annotations.NotNull;


/**
 * A class that implement the game restart and replay function.
 * this class have players' attribute inheritance method and players' inventory clean method.
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
77
78
79
80
81
82
83




84
85
86
87
88







-
-
-
-





        //Party is empty, fainted is full.
        state.getParty().addAll(state.getFainted());
        state.getFainted().removeAll(state.getParty());

        this.inventoryClean();
        this.attributeInheritance();

        // incomplete remake, need to amend later. exactly implementation should depend
        // on other use cases' implementation
        MapGenerator isekai = new MapGenerator(state);
        isekai.generateMap();
        for (Resetable resetable : resetables) {
            resetable.reset();
        }
    }
}

Changes to src/main/java/com/mg105/use_cases/battle/BattleInteractor.java.

22
23
24
25
26
27
28

29
30
31
32
33
34
35
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36







+







    private final static int MAX_UPGRADE_TOKEN_REWARDED = 3;
    private final GameState state;
    private final InventoryInteractor inventoryInteractor;

    private final Saver saver;
    private BattlePresenterInterface presenter;

    private BattleCharacter moving;

    /**
     * Creates a new BattleInteractor with a reference to the GameState.
     *
     * @param state               the GameState to be referred to.
     * @param inventoryInteractor the inventoryInteractor to be referred to.
     * @param saver               an instance of Saver used to save data.
65
66
67
68
69
70
71


72
73
74
75
76
77
78
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81







+
+







        for (int i = 0; i < party.size(); ++i) {
            partyNames[i] = party.get(i).getName();
        }

        for (int i = 0; i < opponents.size(); ++i) {
            opponentNames[i] = opponents.get(i).getName();
        }

        moving = null;

        presenter.setViewNames(partyNames, opponentNames);
    }

    /**
     * Returns whether the given name is associated with a fainted character.
     * Assumes that the inputted name corresponds to a character who was in the encounter at some point.
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
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
200
201
202
203
204
205
206
207
208
209







+
+





-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







     * If encounter is still in progress, get the next moving character.
     * If opponent is moving, choose a random move and random target and use it.
     * returns null iff the battle has ended
     *
     * @return a String of the name of the moving character
     */
    public String roundStart() {
        moving = null;

        int status = getBattleStatus();
        if (Math.abs(status) == 1) {
            //Player either lost or won the battle
            return null;
        } else { //Battle is ongoing
            BattleCharacter moving = state.getCurrEncounter().getMovingCharacter();
            moving = state.getCurrEncounter().getMovingCharacter();

            if (moving.isOpponent()) { //Opponent character is moving
                Random rand = new Random();
                int moveNumber = rand.nextInt(2);

                Move chosenMove;
                if (moveNumber == 0) {
                    chosenMove = moving.getMoveOne();
                } else {
                    chosenMove = moving.getMoveTwo();
                }

                if (!chosenMove.isFriendly()) {
                    ArrayList<BattleCharacter> players = state.getCurrEncounter().getPlayerCharacters();

                    //Choose a random player character to attack
                    int target = rand.nextInt(players.size());

                    this._useMove(chosenMove, moving, players.get(target));
                } else {
                    ArrayList<BattleCharacter> opponents = state.getCurrEncounter().getOpponents();

                    //Choose a random opponent to use the friendly move on
                    int target = rand.nextInt(opponents.size());

                    this._useMove(chosenMove, moving, opponents.get(target));
                }
            }

            //No matter what, the moving BattleCharacter's name is returned
            return moving.getName();
        }
    }

    /**
     * Have the opponents apply an attack.
     */
    public void applyOpponentAttack() {
        assert moving != null;
        assert moving.isOpponent();

        Random rand = new Random();
        int moveNumber = rand.nextInt(2);

        Move chosenMove;
        if (moveNumber == 0) {
            chosenMove = moving.getMoveOne();
        } else {
            chosenMove = moving.getMoveTwo();
        }

        if (!chosenMove.isFriendly()) {
            ArrayList<BattleCharacter> players = state.getCurrEncounter().getPlayerCharacters();

            //Choose a random player character to attack
            int target = rand.nextInt(players.size());

            this._useMove(chosenMove, moving, players.get(target));
        } else {
            ArrayList<BattleCharacter> opponents = state.getCurrEncounter().getOpponents();

            //Choose a random opponent to use the friendly move on
            int target = rand.nextInt(opponents.size());

            this._useMove(chosenMove, moving, opponents.get(target));
        }
    }

    /**
     * Returns the name of every BattleCharacter which can be targeted by the given move.
     * Note: Function should only be called from view when caster is friendly, so method does not accommodate for case
     * where caster is an opponent.
     *
     * @param moveNum    integer representing which of the two Moves is being used.

Deleted src/main/java/com/mg105/user_interface/AlertBox.java.

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












































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.user_interface;

import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javafx.stage.Stage;

/**
 * AlertBox is a component that creates a new window and display a users a message.
 * This message is usually related to if an action the user performed was successful or not.
 */
public class AlertBox {


    /**
     * Displays a modal that must be resolved before switches to a different window
     *
     * @param msg the message to display`
     */

    public void display(String msg) {
        Stage window = new Stage();
        window.initModality(Modality.APPLICATION_MODAL);
        window.setTitle("Alert");
        window.setHeight(400);
        window.setWidth(400);
        window.setResizable(false);
        Label label = new Label();
        label.setText(msg);
        Button close = new Button("Close");
        close.setOnAction(e -> window.close());

        VBox layout = new VBox(10);
        layout.getChildren().addAll(label, close);
        layout.setAlignment(Pos.CENTER);
        Scene scene = new Scene(layout, Color.LIGHTBLUE);
        window.setScene(scene);
        window.showAndWait();
    }
}

Added src/main/java/com/mg105/user_interface/Alerter.java.

















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.user_interface;

import javafx.scene.Parent;
import org.jetbrains.annotations.NotNull;

/**
 * An Alerter can display and handle alerts (modal dialogues).
 */
public interface Alerter {
    /**
     * Display an alert.
     *
     * @param body the actual alert.
     */
    void displayAlert(@NotNull Parent body);
}

Changes to src/main/java/com/mg105/user_interface/BattleMenu.java.

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
200
201
202
203


204
205
206
207
208
209
210
211
212
213
214
215
216


217
218
219
220
221

222
223
224
225
226

227
228
229
230




231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246

247
248
249
250

251
252

253
254
255


256
257
258
259
260




261
262
263
264

265
266
267
268

269
270
271
272
273
274
275








276
277
278
279

280
281

282
283


284
285
286

287
288
289
290
291
292
293
294





295
296
297

298
299
300
301
302
303
304
305



306
307
308
309
310
311
312

313
314
315
316
317
318
319
320


321
322
323
324

325
326
327
328
329
330


331
332
333
334
335
336
337
338
339





340
341
342
343

344
345
346

347
348
349
350
351


352
353
354
355
356
357
358
359
360


361
362
363
364
365
366
367
368

369
370


371
372
373


374
375
376
377
378

379
380
381
382

383
384
385
386
387
388

389
390



391
392
393
394
395






396
397
398

399
400
401
402
403
404
405
406
407
408


409
410
411
412


413
414
415



416
417

418
419
420
421
422
423
424
425
426

427
428
429
430
431
432
433
434

435
436
437

438
439

440
441
442
443
444
445
446


447
448
449
450
451
452
453
454
455
456
457
458
459
460







461
462
463






464
465
466
467
468
469

470
471
472
473
474
475











476
477



478
479
480
481
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261


262
263
264
265
266
267
268
269
270
271
272
273
274


275
276





277





278




279
280
281
282
















283

284


285


286



287
288





289
290
291
292




293




294







295
296
297
298
299
300
301
302




303

304
305


306
307



308





309


310
311
312
313
314



315








316
317
318
319






320





321


322
323




324



325


326
327







328

329
330
331
332
333
334



335



336

337



338
339






340


341
342







343
344


345
346



347
348





349

350


351





352
353


354
355
356





357
358
359
360
361
362



363










364
365



366
367
368



369
370
371


372









373








374



375


376







377
378














379
380
381
382
383
384
385



386
387
388
389
390
391






392






393
394
395
396
397
398
399
400
401
402
403


404
405
406
407
408
409
410


+


+
+
+
+

-
-
-
-
+
+
+
+

-
+
+


-
+




-
+
-
-
+
-
-
+
-
-
-
-

-
-
-
+
+
+
-

-
-
+
+
-
-
-
-
+

-
-
-
+
+
+

-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+





-
+
+

-
+



-
-
-
-
+
+
+
+
+

-
+
-
-
-
+
+

-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+

-
-
-
-
+
+

-
-
-
-
+
+
+
+

-
-
+
+
-
-

-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+

-
+
-
-
+
-

-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+

-
+
-
-
+

-
-
+
-
-
-
+
+
+










-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-










+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









-
-
+
+











-
-
+
+
-
-
-
-
-
+
-
-
-
-
-
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-

-
-
+
-
-
+
-
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-
+
-
-
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
-

+
-
-
+
+
-
-
-
+
-
-
-
-
-

-
-
+
+
+
+
+
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+

-
-
-
-
-
-
+
-
-
-
-
-

-
-
+
+
-
-
-
-
+
-
-
-

-
-
+
+
-
-
-
-
-
-
-

-
+
+
+
+
+

-
-
-
+
-
-
-
+
-

-
-
-
+
+
-
-
-
-
-
-

-
-
+
+
-
-
-
-
-
-
-

+
-
-
+
+
-
-
-
+
+
-
-
-
-
-
+
-

-
-
+
-
-
-
-
-

+
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-

+
+
-
-
-
+
+
+
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
-
-
-
+
-
-
+
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+




package com.mg105.user_interface;

import com.mg105.interface_adapters.InputControllable;
import com.mg105.interface_adapters.battle.BattleMenuInterface;
import com.mg105.interface_adapters.battle.BattlePresenter;
import com.mg105.utils.PartyConstants;
import com.mg105.utils.UIConstants;
import javafx.animation.FadeTransition;
import javafx.animation.TranslateTransition;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.*;
import javafx.util.Duration;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * This class uses JavaFX and is displayed during an active battle.
 */
public class BattleMenu implements EventHandler<ActionEvent>, BattleMenuInterface, Toggleable {
public class BattleMenu implements InputControllable, BattleMenuInterface, Toggleable {

    private final BattlePresenter presenter;
    private final @NotNull BattlePresenter presenter;
    private final String[] playerNames = new String[4];
    private final int[] playerHealth = new int[4];
    private final @NotNull StandardComponentFactory componentFactory;
    private final int[] playerDmg = new int[4];
    private final String[] opponentNames = new String[4];
    private final int[] opponentHealth = new int[4];
    private final int[] opponentDmg = new int[4];

    private final Label p0 = new Label();
    private final Label p1 = new Label();
    private final Label p2 = new Label();
    private final @NotNull Label[] opponentNames;
    private final @NotNull Label[] opponentHps;
    private final @NotNull Label[] opponentDmgs;
    private final Label p3 = new Label();

    private final Label o0 = new Label();
    private final Label o1 = new Label();
    private final @NotNull Label[] playerNames;
    private final @NotNull Label[] playerHps;
    private final Label o2 = new Label();
    private final Label o3 = new Label();

    private final Button nextRound;
    private final @NotNull Label[] playerDmgs;

    private final Button forfeit;
    private final Button moveOne;
    private final Button moveTwo;
    private final @NotNull Label turnHint;
    private final @NotNull Label actionHint;
    private final @NotNull MultiselectGrid options;

    private final Button targetP0;
    private final Button targetP1;
    private final Button targetP2;
    private final @NotNull StackPane layoutWrapper;
    private final @NotNull Group effectLayer;

    private final Button targetP3;
    private final Button targetO0;
    private final Button targetO1;
    private final Button targetO2;
    private final Button targetO3;
    private final GridPane grid;
    private final Scene scene;
    private String moving;
    private int moveNum;
    private boolean needsSelection;
    private String mover;
    private int selectedMove;
    private String selectedTarget;

    /**
     * Creates a new BattleMenu.
     * Sets every UI element, except for labels which are set when the view is toggled on.
     *
     * @param battlePres the BattlePresenter to refer to (following MVP pattern)
     * @param battlePres       the BattlePresenter to refer to (following MVP pattern)
     * @param componentFactory the component factory to use.
     */
    public BattleMenu(BattlePresenter battlePres) {
    public BattleMenu(@NotNull BattlePresenter battlePres, @NotNull StandardComponentFactory componentFactory) {
        this.presenter = battlePres;
        presenter.setView(this);

        grid = new GridPane();
        grid.setVgap(10);
        grid.setHgap(10);
        grid.setAlignment(Pos.CENTER);
        this.componentFactory = componentFactory;

        opponentNames = new Label[4];
        opponentHps = new Label[4];
        opponentDmgs = new Label[4];

        nextRound = new Button("Next Round");
        playerNames = new Label[4];
        nextRound.setId("Next Round");
        nextRound.setOnAction(this);
        grid.add(nextRound, 10, 30);
        playerHps = new Label[4];
        playerDmgs = new Label[4];

        forfeit = new Button("Forfeit");
        forfeit.setId("Forfeit");
        forfeit.setOnAction(this);
        grid.add(forfeit, 10, 2);

        grid.add(p0, 1, 4);
        for (int i = 0; i < 4; i++) {
            opponentNames[i] = componentFactory.createSubtitleLabel("UNSET", true);
            opponentHps[i] = componentFactory.createLabel("UNSET", true);
            opponentDmgs[i] = componentFactory.createLabel("UNSET", true);

            playerNames[i] = componentFactory.createSubtitleLabel("UNSET", true);
            playerHps[i] = componentFactory.createLabel("UNSET", true);
            playerDmgs[i] = componentFactory.createLabel("UNSET", true);
        }

        grid.add(p1, 1, 8);
        grid.add(p2, 1, 12);
        grid.add(p3, 1, 16);
        turnHint = componentFactory.createSubtitleLabel("UNSET", true);
        actionHint = componentFactory.createSubtitleLabel("UNSET", true);

        grid.add(o0, 18, 4);
        grid.add(o1, 18, 8);
        grid.add(o2, 18, 12);
        grid.add(o3, 18, 16);
        HBox hintSlot = new HBox();
        hintSlot.getChildren().addAll(turnHint, actionHint);

        targetP0 = new Button("TARGET");
        targetP0.setOnAction(this);
        targetP0.setVisible(false);
        grid.add(targetP0, 0, 4);
        options = componentFactory.createMultiselectGrid();

        Region optionsSpacer = componentFactory.createFixedHalfSpacer();
        optionsSpacer.setPrefSize(0, 11 * UIConstants.SINGLE_GAP);

        targetP1 = new Button("TARGET");
        targetP1.setOnAction(this);
        HBox optionsSlot = new HBox();
        optionsSlot.getChildren().addAll(optionsSpacer, options.getGrid());
        targetP1.setVisible(false);
        grid.add(targetP1, 0, 8);

        targetP2 = new Button("TARGET");
        targetP2.setOnAction(this);
        targetP2.setVisible(false);
        grid.add(targetP2, 0, 12);
        VBox layout = new VBox();
        layout.setMinHeight(UIConstants.CANVAS_SIZE - 4 * UIConstants.SINGLE_GAP); // ????
        layout.getChildren().addAll(
            makeBlock(opponentNames, opponentHps, opponentDmgs),
            componentFactory.createSpacer(),
            makeBlock(playerNames, playerHps, playerDmgs),
            componentFactory.createSpacer(),
            componentFactory.createSpacer(),
            hintSlot,
            componentFactory.createFixedHalfSpacer(),
            optionsSlot
        );

        targetP3 = new Button("TARGET");
        effectLayer = new Group();
        targetP3.setOnAction(this);
        targetP3.setVisible(false);
        effectLayer.setManaged(false);
        grid.add(targetP3, 0, 16);

        targetO0 = new Button("TARGET");
        targetO0.setOnAction(this);
        layoutWrapper = new StackPane();
        layoutWrapper.setBackground(componentFactory.createBackground(true));
        layoutWrapper.setPadding(componentFactory.createInsets());
        grid.add(targetO0, 19, 4);
        targetO0.setVisible(false);

        targetO1 = new Button("TARGET");
        layoutWrapper.setAlignment(Pos.CENTER);
        layoutWrapper.getChildren().addAll(layout, effectLayer);
    }

        targetO1.setOnAction(this);
        targetO1.setVisible(false);
        grid.add(targetO1, 19, 8);

        targetO2 = new Button("TARGET");
        targetO2.setOnAction(this);
    /**
     * Make a row of characters.
     *
     * @param names the names of the characters.
     * @param hps   the health values of the characters
     * @param dmgs  the damage values of the characters.
        targetO2.setVisible(false);
        grid.add(targetO2, 19, 12);

        targetO3 = new Button("TARGET");
     *
     * @return a layout of a row of characters.
     */
    private @NotNull HBox makeBlock(@NotNull Label[] names, @NotNull Label[] hps, @NotNull Label[] dmgs) {
        HBox block = new HBox();
        targetO3.setOnAction(this);
        targetO3.setVisible(false);
        grid.add(targetO3, 19, 16);
        for (int i = 0; i < names.length; i++) {
            GridPane info = new GridPane();
            info.addRow(0, componentFactory.createLabel("HP: ", true), hps[i]);
            info.addRow(1, componentFactory.createLabel("Dmg: ", true), dmgs[i]);

        moveOne = new Button("");
            VBox stack = new VBox();
        moveOne.setOnAction(this);
        moveOne.setVisible(false);
            stack.getChildren().addAll(names[i], info);

        moveTwo = new Button("");
        moveTwo.setOnAction(this);
            block.getChildren().addAll(stack, componentFactory.createSpacer());
        moveTwo.setVisible(false);

        scene = new Scene(grid, 800, 800);
        }
        block.getChildren().remove(block.getChildren().size() - 1);
        return block;
    }

    /**
     * Sets the names of the player and opponent characters participating in the active battle.
     *
     * @param playerNames   array of name Strings representing player characters.
     * @param opponentNames array of name Strings representing opponents.
     */
    @Override
    public void setNames(String[] playerNames, String[] opponentNames) {
        for (int i = 0; i < 4; ++i) {
        for (int i = 0; i < this.playerNames.length; i++) {
            //Check if any player characters have fainted. Opponent set will always contain four characters initially.
            if (playerNames.length <= i) {
                this.playerNames[i] = "FAINTED";
                this.playerHealth[i] = 0;
                this.playerDmg[i] = 0;
            } else {
                this.playerNames[i] = playerNames[i];
                this.playerHealth[i] = presenter.givenCharacterHealth(this.playerNames[i]);
                this.playerDmg[i] = presenter.givenCharacterDamage(this.playerNames[i]);
            }

            this.opponentNames[i] = opponentNames[i];
            this.playerNames[i].setText(PartyConstants.ALL_PARTY_MEMBER_NAMES[i]); // HACK: we shouldn't just ignore the playerNames parameter, but any discrepancy that this will cause will get instantly fixed somewhere else.
            this.opponentNames[i].setText(opponentNames[i]);
            this.opponentHealth[i] = presenter.givenCharacterHealth(this.opponentNames[i]);
            this.opponentDmg[i] = presenter.givenCharacterDamage(this.opponentNames[i]);
        }
    }

    /**
     * Updates the display corresponding to the given affected character.
     *
     * @param character the character who needs to be updated on the screen.
     */
    @Override
    public void updateCharacter(String character) {
        for (int i = 0; i < playerNames.length; i++) {
        if (playerNames[0].equals(character)) {
            updateCharacterData(character, 0, p0, false);
        } else if (playerNames[1].equals(character)) {
            updateCharacterData(character, 1, p1, false);
        } else if (playerNames[2].equals(character)) {
            updateCharacterData(character, 2, p2, false);
        } else if (playerNames[3].equals(character)) {
            updateCharacterData(character, 3, p3, false);
        } else if (opponentNames[0].equals(character)) {
            updateCharacterData(character, 0, o0, true);
        } else if (opponentNames[1].equals(character)) {
            updateCharacterData(character, 1, o1, true);
        } else if (opponentNames[2].equals(character)) {
            updateCharacterData(character, 2, o2, true);
        } else if (opponentNames[3].equals(character)) {
            updateCharacterData(character, 3, o3, true);
            if (playerNames[i].getText().equals(character)) {
                updateCharacter(character, playerNames[i], playerHps[i], playerDmgs[i], true);
                return;
            }

            if (opponentNames[i].getText().equals(character)) {
                updateCharacter(character, opponentNames[i], opponentHps[i], opponentDmgs[i], true);
                return;
            }
        }
    }

    /**
     * Updates the information once the slot of the update is known.
     *
     * @param character the character to update.
     * @param name      the name location.
     * @param hp        the hp location.
     * @param dmg       the damage location.
     * @param effect    if an effect should be drawn to visually represent the change.
     */
    private void updateCharacter(@NotNull String character, @NotNull Label name,
                                 @NotNull Label hp, @NotNull Label dmg, boolean effect) {
        int startHp = Integer.MIN_VALUE;
        int startDmg = Integer.MIN_VALUE;
        if (effect) {
            startHp = Integer.parseInt(hp.getText());
            startDmg = Integer.parseInt(dmg.getText());
        }

        int deltaHp = Integer.MIN_VALUE;
        int deltaDmg = Integer.MIN_VALUE;

        if (presenter.givenCharacterFainted(character)) {
            name.setText("FAINTED");
            hp.setText("N/A");
            dmg.setText("N/A");

            if (effect) {
                deltaHp = -startHp;
                deltaDmg = 0;
            }
        } else {
            final int endHp = presenter.givenCharacterHealth(character);
            final int endDmg = presenter.givenCharacterDamage(character);

            hp.setText(Integer.toString(endHp));
            dmg.setText(Integer.toString(endDmg));

            if (effect) {
                deltaHp = endHp - startHp;
                deltaDmg = endDmg - startDmg;
            }
        }

        if (effect) {
            Bounds anchorLocation = name.localToScene(name.getBoundsInLocal());
            if (anchorLocation == null) {
                return;
            }

            HBox effectBox = new HBox();
            effectBox.getChildren().addAll(
                componentFactory.createNumberedLabel(deltaHp, true),
                componentFactory.createFixedHalfSpacer(),
                componentFactory.createNumberedLabel(deltaDmg, true)
            );
            effectBox.setLayoutX(anchorLocation.getCenterX());
            effectBox.setLayoutY(anchorLocation.getMinY() - (int)(UIConstants.SINGLE_GAP / 2));
            effectBox.setTranslateX(0);
            effectBox.setTranslateY(0);

            Duration duration = Duration.millis(1000);

            TranslateTransition translate = new TranslateTransition();
            translate.setDuration(duration);
            translate.setByY(-UIConstants.SINGLE_GAP);
            translate.setNode(effectBox);

            FadeTransition fade = new FadeTransition();
            fade.setDuration(duration);
            fade.setFromValue(1);
            fade.setToValue(0);
            fade.setNode(effectBox);
            fade.setOnFinished((ActionEvent event) -> effectLayer.getChildren().remove(effectBox));

            translate.play();
            fade.play();
            effectLayer.getChildren().add(effectBox);
        }
    }

    /**
     * Get the scene of this toggleable object.  It is this scene that will be displayed.
     *
     * @return the scene to be displayed.
     */
    @Override
    public @NotNull Scene getScene() {
        return this.scene;
    public @NotNull Pane getScene() {
        return layoutWrapper;
    }

    /**
     * Set the visibility of this component.
     *
     * @param isVisible true if the Toggleable is now visible, false otherwise.  If false the Toggleable is expected
     *                  to do nothing on ANY user inputs.
     */
    @Override
    public void toggle(boolean isVisible) {
        if (isVisible) {
            presenter.startBattle(); //Will call setNames and update character data.

            presenter.startBattle(); // Will call setNames and update character data.
            for (int i = 0; i < playerNames.length; i++) {
            setupCharacterLabel(p0, 0, false);
            setupCharacterLabel(p1, 1, false);
            setupCharacterLabel(p2, 2, false);
            setupCharacterLabel(p3, 3, false);

                updateCharacter(playerNames[i].getText(), playerNames[i], playerHps[i], playerDmgs[i], false);
            setupCharacterLabel(o0, 0, true);
            setupCharacterLabel(o1, 1, true);
            setupCharacterLabel(o2, 2, true);
            setupCharacterLabel(o3, 3, true);

                updateCharacter(opponentNames[i].getText(), opponentNames[i], opponentHps[i], opponentDmgs[i], false);
            nextRound.setDisable(false);
        }
    }

            }

            startTurn();
        }
    /**
     * Handles button events.
     * NextRound will start the next round, which is handled in BattlePresenter.
     * The two move buttons represent a move to be selected, and will cause target buttons to appear next to valid
     * target characters and the move buttons to disappear.
     * The target buttons only become visible when the character they represent can be targeted with the selected move,
     * and pressing them will apply the move onto the character and make the target buttons disappear.
     *
     * @param event the event which occurred.
     */
    @Override
    public void handle(ActionEvent event) {
        Object source = event.getSource();
        if (source.equals(nextRound)) {
            nextRound.setDisable(true);

    }
            moving = presenter.roundStart();

            if (moving == null) {
                //Battle ended
    /**
                presenter.endBattle();
                return;
     * Create a move description.
            }

            //moving != null
     *
     * @param name the name of the move.
            if (playerNames[0].equals(moving)) {
                int[] moveStats = presenter.givenCharacterMoveStats(moving);
                String[] moveNames = presenter.givenCharacterMoveNames(moving);

                grid.add(moveOne, 2, 4);
     * @param hp   the hp damage of the move.
     * @param dmg  the dmg damage of the move.
     *
     * @return the move description.
                moveOne.setVisible(true);
                grid.add(moveTwo, 3, 4);
                moveTwo.setVisible(true);

     */
                moveOne.setText(moveNames[0] + "\n Hp: " + moveStats[0] + ", Dmg: " + moveStats[1]);
                moveTwo.setText(moveNames[1] + "\n Hp: " + moveStats[2] + ", Dmg: " + moveStats[3]);
            } else if (playerNames[1].equals(moving)) {
                int[] moveStats = presenter.givenCharacterMoveStats(moving);
    private @NotNull VBox makeMoveStack(@NotNull String name, int hp, int dmg) {
                String[] moveNames = presenter.givenCharacterMoveNames(moving);

                grid.add(moveOne, 2, 8);
                moveOne.setVisible(true);
                grid.add(moveTwo, 3, 8);
                moveTwo.setVisible(true);

        GridPane info = new GridPane();
        info.addRow(0,
            componentFactory.createLabel("HP: ", true),
            componentFactory.createLabel(Integer.toString(hp), true)
        );
        info.addRow(1,
            componentFactory.createLabel("Dmg: ", true),
            componentFactory.createLabel(Integer.toString(dmg), true)
                moveOne.setText(moveNames[0] + "\n Hp: " + moveStats[0] + ", Dmg: " + moveStats[1]);
                moveTwo.setText(moveNames[1] + "\n Hp: " + moveStats[2] + ", Dmg: " + moveStats[3]);
            } else if (playerNames[2].equals(moving)) {
                int[] moveStats = presenter.givenCharacterMoveStats(moving);
        );
                String[] moveNames = presenter.givenCharacterMoveNames(moving);

        VBox stack = new VBox();
                grid.add(moveOne, 2, 12);
                moveOne.setVisible(true);
        stack.getChildren().addAll(componentFactory.createLabel(name, true), info);
        return stack;
                grid.add(moveTwo, 3, 12);
                moveTwo.setVisible(true);

    }
                moveOne.setText(moveNames[0] + "\n Hp: " + moveStats[0] + ", Dmg: " + moveStats[1]);
                moveTwo.setText(moveNames[1] + "\n Hp: " + moveStats[2] + ", Dmg: " + moveStats[3]);
            } else if (playerNames[3].equals(moving)) {
                int[] moveStats = presenter.givenCharacterMoveStats(moving);
                String[] moveNames = presenter.givenCharacterMoveNames(moving);

                grid.add(moveOne, 2, 16);
                moveOne.setVisible(true);
    /**
     * Start a new turn.
     */
    private void startTurn() {
        options.reset();
                grid.add(moveTwo, 3, 16);
                moveTwo.setVisible(true);

        options.addColumn(componentFactory.createLabel("Forfeit", true));
                moveOne.setText(moveNames[0] + "\n Hp: " + moveStats[0] + ", Dmg: " + moveStats[1]);
                moveTwo.setText(moveNames[1] + "\n Hp: " + moveStats[2] + ", Dmg: " + moveStats[3]);
            } else if (opponentNames[0].equals(moving) || opponentNames[1].equals(moving) ||
                opponentNames[2].equals(moving) || opponentNames[3].equals(moving)) {
                //If opponent moved, re-enable nextRound button
                nextRound.setDisable(false);
            }
        } else if (source.equals(forfeit)) {

        mover = presenter.roundStart();
        if (mover == null) {
            presenter.endBattle();
        } else if (source.equals(moveOne)) {
            moveNum = 1;
            displayTargets();
        } else if (source.equals(moveTwo)) {
            moveNum = 2;
            displayTargets();
        }
        } else if (source.equals(targetP0)) {
            targetP0.setVisible(false);
            targetP1.setVisible(false);
            targetP2.setVisible(false);
            targetP3.setVisible(false);

            presenter.executeTurn(moveNum, moving, playerNames[0]);

        needsSelection = Arrays.stream(playerNames).anyMatch(x -> x.getText().equals(mover));
        selectedMove = -1;
            //Re-enable nextRound button after a move has been made.
            nextRound.setDisable(false);
        } else if (source.equals(targetP1)) {
            targetP0.setVisible(false);
        selectedTarget = null;
            targetP1.setVisible(false);
            targetP2.setVisible(false);
            targetP3.setVisible(false);

            presenter.executeTurn(moveNum, moving, playerNames[1]);

        advanceOptions();
    }
            //Re-enable nextRound button after a move has been made.
            nextRound.setDisable(false);
        } else if (source.equals(targetP2)) {
            targetP0.setVisible(false);
            targetP1.setVisible(false);
            targetP2.setVisible(false);
            targetP3.setVisible(false);

            presenter.executeTurn(moveNum, moving, playerNames[2]);
    /**
     * Advance the possible options to the correct state.
     */
    private void advanceOptions() {
        turnHint.setText("It's " + mover + "'s turn... ");

            //Re-enable nextRound button after a move has been made.
            nextRound.setDisable(false);
        } else if (source.equals(targetP3)) {
        if (needsSelection) {
            targetP0.setVisible(false);
            targetP1.setVisible(false);
            targetP2.setVisible(false);
            actionHint.setVisible(true);
            targetP3.setVisible(false);

            presenter.executeTurn(moveNum, moving, playerNames[3]);

            //Re-enable nextRound button after a move has been made.
            if (selectedMove < 0) {
                actionHint.setText("Select a move:");
            nextRound.setDisable(false);
        } else if (source.equals(targetO0)) {
            targetO0.setVisible(false);
            targetO1.setVisible(false);
            targetO2.setVisible(false);
            targetO3.setVisible(false);

            presenter.executeTurn(moveNum, moving, opponentNames[0]);

                String[] moveNames = presenter.givenCharacterMoveNames(mover);
                int[] moveStats = presenter.givenCharacterMoveStats(mover);
            //Re-enable nextRound button after a move has been made.
            nextRound.setDisable(false);
        } else if (source.equals(targetO1)) {
            targetO0.setVisible(false);
            targetO1.setVisible(false);
            targetO2.setVisible(false);
            targetO3.setVisible(false);

                options.addColumn(
            presenter.executeTurn(moveNum, moving, opponentNames[1]);

                    makeMoveStack(moveNames[0], moveStats[0], moveStats[1]),
                    makeMoveStack(moveNames[1], moveStats[2], moveStats[3])
            //Re-enable nextRound button after a move has been made.
            nextRound.setDisable(false);
        } else if (source.equals(targetO2)) {
                );
            } else if (selectedTarget == null) {
            targetO0.setVisible(false);
            targetO1.setVisible(false);
            targetO2.setVisible(false);
            targetO3.setVisible(false);

                actionHint.setText("Select a target:");
            presenter.executeTurn(moveNum, moving, opponentNames[2]);

            //Re-enable nextRound button after a move has been made.
            nextRound.setDisable(false);
                options.selectActive();
        } else if (source.equals(targetO3)) {
            targetO0.setVisible(false);
            targetO1.setVisible(false);
            targetO2.setVisible(false);
            targetO3.setVisible(false);

                options.addColumn();
            presenter.executeTurn(moveNum, moving, opponentNames[3]);

                for (String target : presenter.retrieveTargets(selectedMove, mover)) {
                    options.addToColumn(componentFactory.createLabel(target, true));
                }
            //Re-enable nextRound button after a move has been made.
            nextRound.setDisable(false);
        }
    }

            }
        } else {
            actionHint.setVisible(false);

            options.addColumn(componentFactory.createLabel("Next Move", true));
        }
    /**
     * Helper function for updateCharacter.
     *

     * @param character  name String of the character to be updated.
     * @param position   index of the character's data in the data arrays.
     * @param lbl        Label object to be updated.
     * @param isOpponent boolean for whether the character is an opponent or not.
     */
    private void updateCharacterData(String character, int position, Label lbl, boolean isOpponent) {
        if (presenter.givenCharacterFainted(character)) {
            lbl.setText("FAINTED");
            return;
        }
        while (options.activeRight()); // Make sure we are in the right-most slot, for convenience
    }
        int[] hpData;
        int[] dmgData;
        String[] nameData;

    @Override
    public void interpret(String key) {
        if (isOpponent) {
            hpData = opponentHealth;
            dmgData = opponentDmg;
        switch (key) {
            case "w" -> options.activeUp();
            case "s" -> options.activeDown();
            nameData = opponentNames;
        } else {
            case "a" -> {
            hpData = playerHealth;
            dmgData = playerDmg;
            nameData = playerNames;
        }
        hpData[position] = presenter.givenCharacterHealth(character);
        dmgData[position] = presenter.givenCharacterDamage(character);
        lbl.setText(nameData[position] + "\n Hp: " + hpData[position] + ", Dmg: " + dmgData[position]);
    }

                if (options.getActive().x == 2) {
    /**
     * Helper function for moveOne/moveTwo button click events.
     * Removes the two buttons, retrieves targets based on the chosen move, displays respective target buttons.
     */
    private void displayTargets() {
        moveOne.setVisible(false);
        moveTwo.setVisible(false);

                    actionHint.setText("Select a move:");
        grid.getChildren().remove(moveOne);
        grid.getChildren().remove(moveTwo);

                    selectedMove = -1;
        ArrayList<String> targets = presenter.retrieveTargets(moveNum, moving);

                    options.popColumn();
        for (String s : targets) {
            if (playerNames[0].equals(s)) {
                targetP0.setVisible(true);
            } else if (playerNames[1].equals(s)) {
                targetP1.setVisible(true);
            } else if (playerNames[2].equals(s)) {
                targetP2.setVisible(true);
                } else {
                    options.activeLeft();
            } else if (playerNames[3].equals(s)) {
                targetP3.setVisible(true);
            } else if (opponentNames[0].equals(s)) {
                targetO0.setVisible(true);
            } else if (opponentNames[1].equals(s)) {
                targetO1.setVisible(true);
            } else if (opponentNames[2].equals(s)) {
                targetO2.setVisible(true);
            } else if (opponentNames[3].equals(s)) {
                targetO3.setVisible(true);
            }
        }
    }

                }
            }
            case "d" -> {
                if (options.getActive().x == 0) {
                    options.activeRight();
                }
            }
    /**
     * Helper function to initialize the character labels.
     *
            case " " -> {
                if (options.getActive().x == 0) { // Forfeit
                    presenter.endBattle();
                    return;
                }

     * @param lbl        Label object corresponding to the character.
     * @param position   Integer representing index of character's data in data arrays.
     * @param isOpponent Boolean of whether the character is an opponent.
     */
    private void setupCharacterLabel(Label lbl, int position, boolean isOpponent) {
        if (isOpponent) {
                if (needsSelection) {
            lbl.setText(opponentNames[position] + "\n Hp: " + opponentHealth[position] + ", Dmg: " +
                opponentDmg[position]);
        } else {
            if (playerNames[position].equals("FAINTED")) {
                lbl.setText("FAINTED");
            } else {
                    if (options.getActive().x == 1) {
                        selectedMove = options.getActive().y + 1; // options are 1 or 2 not 0 or 1
                        advanceOptions();
                    } else if (options.getActive().x == 2) {
                        selectedTarget = presenter.retrieveTargets(selectedMove, mover).get(options.getActive().y);

                        presenter.executeTurn(selectedMove, mover, selectedTarget);

                        startTurn();
                    }
                } else {
                lbl.setText(playerNames[position] + "\n Hp: " + playerHealth[position] + ", Dmg: " +
                    playerDmg[position]);
                    presenter.applyOpponentAttack();
                    startTurn();
                }
            }
        }
    }
}

Added src/main/java/com/mg105/user_interface/IntroDisplay.java.

























































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.user_interface;

import com.mg105.interface_adapters.InputControllable;
import com.mg105.interface_adapters.Toggler;
import com.mg105.utils.UIConstants;
import javafx.animation.AnimationTimer;
import javafx.animation.FadeTransition;
import javafx.event.ActionEvent;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.TilePane;
import javafx.util.Duration;
import org.jetbrains.annotations.NotNull;

import java.util.LinkedList;
import java.util.Queue;

/**
 * IntroDisplay displays the intro text when you start a new game.
 */
public class IntroDisplay implements Toggleable, InputControllable {
    private final @NotNull Toggler toggler;
    private final @NotNull KeymapDisplay keymap;
    private final @NotNull StandardComponentFactory componentFactory;
    private final @NotNull TilePane pane;
    private final @NotNull Label[] messages;
    private BodyAnimation animations;

    /**
     * Create a new intro display.
     *
     * @param toggler          the toggler used to toggle components.
     * @param keymap           the keymap display.
     * @param componentFactory the component factory.
     */
    public IntroDisplay(@NotNull Toggler toggler, @NotNull KeymapDisplay keymap, @NotNull StandardComponentFactory componentFactory) {
        this.toggler = toggler;
        this.keymap = keymap;
        this.componentFactory = componentFactory;

        pane = new TilePane(Orientation.VERTICAL);
        pane.setBackground(componentFactory.createBackground(false));
        pane.setPadding(componentFactory.createInsets());
        pane.setVgap(UIConstants.SINGLE_GAP);
        pane.setAlignment(Pos.CENTER);

        messages = new Label[]{
            componentFactory.createLabel("You cannot recall anything but the endless action of putting one foot in front of the other, traversing this empty landscape.", false),
            componentFactory.createLabel("Suddenly, you look up, startled out of your reverie. An imposing mountain looms before you. Your party steps forward, drawn to it for some inexplicable reason.", false),
            componentFactory.createLabel("You crane your neck to see if you can make out the peak, but the morning mist impedes your view.", false),
            componentFactory.createLabel("You all know you cannot go back to wherever you came from. You have to keep going. You have to climb the mountain.", false),
            componentFactory.createLabel("Maybe whatever waits for you up there will remind you of who you are... and what you’re searching for.", false),
            componentFactory.createLabel("Press any key to continue...", false)
        };
        for (Label l : messages) {
            l.setWrapText(true);
            l.setAlignment(Pos.CENTER);
            l.setMaxWidth(UIConstants.CANVAS_SIZE - 2 * UIConstants.SINGLE_GAP);
        }
        pane.getChildren().addAll(messages);
    }

    @Override
    public void interpret(String key) {
        if (animations != null && animations.advance()) {
            keymap.addExtraHints(componentFactory.createLabel("Pressing 'k' starts the game.", true));

            toggler.toggle(Toggler.ToggleableComponent.CONTROLS);
            toggler.toggle(Toggler.ToggleableComponent.INTRO);
        }
    }

    @Override
    public @NotNull Pane getScene() {
        return pane;
    }

    @Override
    public void toggle(boolean isVisible) {
        if (isVisible) {
            animations = new BodyAnimation();
            animations.start();
        } else {
            animations.stop();
            animations = null;
        }
    }

    /**
     * BodyAnimation slowly fades in the messages one by one.
     */
    private class BodyAnimation extends AnimationTimer {
        private long last;
        private final @NotNull Queue<FadeTransition> transitions;

        /**
         * Create a new BodyAnimation.
         */
        public BodyAnimation() {
            last = 0;

            transitions = new LinkedList<>();
            for (Label l : messages) {
                l.setOpacity(0);
                FadeTransition fade = new FadeTransition();
                fade.setDuration(Duration.millis(2500));
                fade.setFromValue(0);
                fade.setToValue(1);
                fade.setOnFinished((ActionEvent value) -> l.setOpacity(1));
                fade.setNode(l);
                transitions.add(fade);
            }

            advance(); // Don't want to wait 2 seconds for the first one to start
        }

        /**
         * Trigger the next animation if more than 2 seconds have passed.
         *
         * @param now
         *            The timestamp of the current frame given in nanoseconds. This
         *            value will be the same for all {@code AnimationTimers} called
         *            during one frame.
         */
        @Override
        public void handle(long now) {
            if (last == 0) {
                last = now;
            } else if (now - last > 1750e6 && advance()) {
                stop();
            }
        }

        /**
         * Advance the animations by one.
         *
         * @return true if this did nothing.
         */
        public boolean advance() {
            if (transitions.isEmpty()) {
                return true;
            }

            last = 0;
            transitions.remove().play();

            return false;
        }
    }
}

Changes to src/main/java/com/mg105/user_interface/InventoryDisplay.java.

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
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

221





222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252


+
+



+
-
-
+
+
-

-
-
-
+
+
+


+

+
+





-
-
+
+
+
+
+
+
+
+

-
-
-
-
-
+
+
+
+
+




-
+
+
+

-
-
+
+
+

+
+
-
-
+
+
-
-
-
+
+
+
+
-
-
-
+
+
+
+
+
+
-
-
-
+
+
-
-
-
+
+
-
-
-
-
-
-
+
+
-
-
-
-
+
+
-
-
-
+
+
-
-
-
-
+
+
-
-
-
+
+







-

-
-
+
+











-
-
+
+
-
-
-
+
+
+
-
-
+
-
-
-
-
-
+
+
+
+
+
+
-

-









-
+
-












-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-












+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-

-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


package com.mg105.user_interface;

import com.mg105.interface_adapters.InputControllable;
import com.mg105.interface_adapters.PartyGetter;
import com.mg105.interface_adapters.inventory.InventoryController;
import com.mg105.interface_adapters.inventory.InventoryViewInterface;
import com.mg105.utils.PartyConstants;
import com.mg105.utils.UIConstants;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.layout.Pane;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A user interface class that displays the state of the inventory to the user and allows them to
 * perform actions that relate to the inventory and its items.
 */

public class InventoryDisplay implements InventoryViewInterface, Toggleable {
public class InventoryDisplay implements InventoryViewInterface, Toggleable, InputControllable {
    private final @NotNull InventoryController controller;
    private final @NotNull PartyGetter partyGetter;
    private boolean isVisible = false;
    private final @NotNull StandardComponentFactory componentFactory;
    private final @NotNull MultiselectGrid options;
    private final @NotNull TilePane layoutWrapper;
    private final @NotNull Label hintLabel;

    private final HashMap<String, HBox> itemNameToInfo = new HashMap<>();
    private final InventoryController controller;
    private Boolean isVisible = false;
    private VBox itemsDisplay;
    private String characterSelected = PartyConstants.ALL_PARTY_MEMBER_NAMES[0];
    private final @NotNull Map<String, String> descriptionCache;
    private final @NotNull Map<String, Boolean> usableCache;
    private final @NotNull Map<String, String> quantityCache;
    private final @NotNull List<String> nameCache;


    /**
     * Creates a new instance of inventory display
     *
     * @param controller an object that interprets user inputs to make changes about the inventory
     * @param controller       an object that interprets user inputs to make changes about the inventory
     * @param componentFactory the component factory used to create UI components
     * @param partyGetter      the party getter used to get party information.
     */

    public InventoryDisplay(InventoryController controller) {
    public InventoryDisplay(@NotNull InventoryController controller,
                            @NotNull StandardComponentFactory componentFactory,
                            @NotNull PartyGetter partyGetter) {
        this.controller = controller;
        this.componentFactory = componentFactory;
        this.partyGetter = partyGetter;
    }


        layoutWrapper = new TilePane();
    /**
     * Returns the character selected by the user
     *
        layoutWrapper.setPadding(componentFactory.createInsets());
        layoutWrapper.setAlignment(Pos.CENTER);
        layoutWrapper.setBackground(componentFactory.createBackground(true));

     * @return the character selected by the user
     */
    private String getCharacterSelected() {
        VBox layout = new VBox();
        layout.setPrefSize(
            UIConstants.CANVAS_SIZE - 2 * UIConstants.SINGLE_GAP,
            UIConstants.CANVAS_SIZE - 2 * UIConstants.SINGLE_GAP
        );
        layoutWrapper.getChildren().add(layout);
        return this.characterSelected;
    }


        options = componentFactory.createMultiselectGrid();
    /**
     * Returns the dropdown where user can select a party member
     *
        layout.getChildren().add(options.getGrid());

     * @return the dropdown where user can select a party member
     */
    private @NotNull ComboBox<String> buildCharacterDropdown() {
        ComboBox<String> partySelector = new ComboBox<>();
        partySelector.getItems().addAll(PartyConstants.ALL_PARTY_MEMBER_NAMES);
        partySelector.setOnAction(e -> this.characterSelected = partySelector.getSelectionModel().getSelectedItem());
        layout.getChildren().add(componentFactory.createSpacer());
        layout.getChildren().add(componentFactory.createSubtitleLabel("Hint(s):", true));
        partySelector.setValue(characterSelected);
        return partySelector;
    }


        hintLabel = componentFactory.createLabel("UNSET TEXT", true);
    /**
     * Returns the component that really has all the details necessary for the scene
     *
        layout.getChildren().add(hintLabel);

     * @return the component that really has all the details necessary for the scene
     */
    private @NotNull VBox buildLayout() {
        this.itemsDisplay = new VBox(10);
        descriptionCache = new HashMap<>();
        usableCache = new HashMap<>();
        this.itemsDisplay.getChildren().add(new Label("Inventory"));
        controller.getInventoryDetails();
        return new VBox(5, this.itemsDisplay, buildCharacterDropdown());
        quantityCache = new HashMap<>();
        nameCache = new ArrayList<>();
    }

    /**
     * Returns the scene that displays the information for the Inventory
     *
     * @return the scene that displays the information for the Inventory.
     */

    @Override
    public @NotNull Scene getScene() {
        return new Scene(buildLayout(), 800, 800, Color.LIGHTBLUE);
    public @NotNull Pane getScene() {
        return layoutWrapper;
    }

    /**
     * Changes the state of the InventoryDisplay based on if the inventory display is shown in the ui
     *
     * @param isVisible true if the Toggleable is now visible, false otherwise.  If false the Toggleable is expected
     *                  to do nothing on ANY user inputs.
     */
    @Override
    public void toggle(boolean isVisible) {
        this.isVisible = isVisible;
    }


        if (isVisible) {
    /**
     * Removes an item from the inventory ui.
     * Precondition there must not be an items of the type name in the inventory
            nameCache.clear();
            controller.getInventoryDetails();
            options.reset();
     *
     * @param name the name of the item to be removed
            if (nameCache.size() > 0) {
     */
    @Override
    public void removeItemView(String name) {
        HBox itemInfo = this.itemNameToInfo.get(name);
        if (itemInfo == null) {
                options.addColumn();
                for (String name : nameCache) {
                    options.addToColumn(componentFactory.createLabel(name + " (" + quantityCache.get(name) + ")", true));
                }
            }
            updateInfo();
            return;
        }
        itemsDisplay.getChildren().remove(itemInfo);
    }

    /**
     * Opens a window displaying a message to the user
     *
     * @param msg the message to alert the user with
     */
    @Override
    public void alert(String msg) {
        AlertBox alert = new AlertBox();
        componentFactory.createAlert(msg, true);
        alert.display(msg);
    }

    /**
     * Adds the details of the item to the display
     *
     * @param name        the name of the item to add
     * @param description the description of the item to add
     * @param isUsable    true iff the item can be used by a user
     * @param quantity    the number of items of this type in the Inventory
     */
    @Override
    public void addItemView(String name, String description, boolean isUsable, String quantity) {
        if (!this.isVisible) {
            return;
        }
        Label nameLabel = new Label(name);
        Label descriptionLabel = new Label(description);
        Label quantityLabel = new Label(quantity);
        Button removeItem = new Button("Remove Item");
        removeItem.setOnAction(e ->
            controller.removeItem(name));
        HBox info = new HBox(10);

        changeItemView(name, description, isUsable, quantity);
        if (isUsable) {
            info.getChildren().addAll(nameLabel, descriptionLabel, quantityLabel, getUseItemButton(name), removeItem);
        } else {
            info.getChildren().addAll(nameLabel, descriptionLabel, quantityLabel, removeItem);
        }

        nameCache.add(name);
        itemsDisplay.getChildren().add(info);
        itemNameToInfo.put(name, info);
    }

    /**
     * Changes the details of an already displayed item and displays the changes
     *
     * @param name        the name of the item to change details about
     * @param description the description of the item
     * @param isUsable    true iff the item is usable
     * @param quantity    the number of items of this type in the Inventory
     */
    @Override
    public void changeItemView(String name, String description, boolean isUsable, String quantity) {
        descriptionCache.put(name, description);
        usableCache.put(name, isUsable);
        quantityCache.put(name, quantity);

        HBox currentInfo = itemNameToInfo.get(name);
        if (currentInfo != null) {
            itemNameToInfo.remove(name);
            itemsDisplay.getChildren().remove(currentInfo);
        }
    }

    /**
     * Removes an item from the inventory ui.
     * Precondition there must not be an items of the type name in the inventory
     *
     * @param name the name of the item to be removed
     */
    @Override
    public void removeItemView(String name) {
        descriptionCache.remove(name);
        usableCache.remove(name);
        quantityCache.remove(name);
        nameCache.remove(name);
    }

    @Override
    public void interpret(String key) {
        if (!isVisible) {
            return;
        }
        addItemView(name, description, isUsable, quantity);
    }

    /**
     * Creates a use item button
     *
     * @param name of item to create use item button

        switch (key) {
            case "w" -> options.activeUp();
            case "s" -> options.activeDown();
            case "a" -> {
                if (options.getActive().x > 0) {
                    options.popColumn();
                }
            }
            case " " -> {
                if (options.isEmpty()) {
                    return;
                }

                if (options.getActive().x == 0) {
                    options.selectActive();
                    options.addColumn(componentFactory.createLabel("Remove Item", true));
                    if (usableCache.get(nameCache.get(options.getActive().y))) {
                        options.addToColumn(componentFactory.createLabel("Use Item", true));
                    }
                    options.activeRight();
                } else if (options.getActive().x == 1) {
                    if (options.getActive().y == 0) {
                        controller.removeItem(nameCache.get(options.getSelected().get(0)));
                        toggle(true);
                    } else {
                        options.selectActive();
                        options.addColumn();
                        for (String name : PartyConstants.ALL_PARTY_MEMBER_NAMES) {
                            options.addToColumn(componentFactory.createLabel(name, true));
                        }
                        options.activeRight();
                    }
                } else {
                    controller.useItem(
                        nameCache.get(options.getSelected().get(0)),
                        PartyConstants.ALL_PARTY_MEMBER_NAMES[options.getActive().y]
                    );
                    toggle(true);
                }
            }
        }
        updateInfo();
    }

    /**
     * Update the hint bottom text.
     * @return the use item button
     */
    private Button getUseItemButton(String name) {
        Button useItem = new Button("Use Item");
        useItem.setOnAction(e ->
            controller.useItem(name, getCharacterSelected()));
        return useItem;
    private void updateInfo() {
        if (options.isEmpty()) {
            options.getGrid().setVisible(false);
            hintLabel.setText("Inventory is empty");
            return;
        }

        options.getGrid().setVisible(true);

        if (options.getActive().x == 0) {
            hintLabel.setText(descriptionCache.get(nameCache.get(options.getActive().y)));
            return;
        }

        if (options.getActive().x == 1) {
            if (options.getActive().y == 0) {
                hintLabel.setText("Drop this item from your inventory.");
            } else {
                hintLabel.setText("Use this item on a character.");
            }
            return;
        }

        String name = PartyConstants.ALL_PARTY_MEMBER_NAMES[options.getActive().y];
        if (partyGetter.isFainted(name)) {
            hintLabel.setText(name + " is fainted.");
        } else {
            hintLabel.setText(name + " has " + partyGetter.getHp(name) + "/" + partyGetter.getMaxHp(name) + " HP and " + partyGetter.getDmg(name) + " Dmg.");
        }
    }
}

Added src/main/java/com/mg105/user_interface/KeymapDisplay.java.










































































































































































































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
200
201
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.user_interface;

import com.mg105.utils.ColorConstants;
import com.mg105.utils.UIConstants;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;

/**
 * Display the keyboard mappings.
 */
public class KeymapDisplay implements Toggleable, EventHandler<KeyEvent> {
    private static final Border KEY_BORDER_UNSELECTED = new Border(new BorderStroke(
        ColorConstants.WALL,
        BorderStrokeStyle.SOLID,
        new CornerRadii((int)(UIConstants.SINGLE_GAP / 4)),
        new BorderWidths((int)(UIConstants.SINGLE_GAP / 8))
    ));
    private static final Border KEY_BORDER_SELECTED = new Border(new BorderStroke(
        ColorConstants.ACTIVE,
        BorderStrokeStyle.SOLID,
        new CornerRadii((int)(UIConstants.SINGLE_GAP / 4)),
        new BorderWidths((int)(UIConstants.SINGLE_GAP / 8))
    ));

    private final @NotNull TilePane pane;
    private final @NotNull VBox layout;
    private final int layoutSize;
    private boolean isVisible = false;
    private final @NotNull Map<String, Label> buttons;

    /**
     * Create a new keymap display.
     *
     * @param componentFactory the component factory used to create components.
     */
    public KeymapDisplay(StandardComponentFactory componentFactory) {
        pane = new TilePane(Orientation.VERTICAL);
        pane.setBackground(componentFactory.createBackground(true));
        pane.setPadding(componentFactory.createInsets());
        pane.setAlignment(Pos.CENTER);

        layout = new VBox();
        layout.setPrefHeight(UIConstants.CANVAS_SIZE - 2 * UIConstants.SINGLE_GAP);
        pane.getChildren().add(layout);

        GridPane grid = new GridPane();
        grid.setHgap(UIConstants.SINGLE_GAP);
        grid.setVgap(UIConstants.SINGLE_GAP);
        grid.setPrefWidth(UIConstants.CANVAS_SIZE - 2 * UIConstants.SINGLE_GAP);
        ColumnConstraints keyConstraints = new ColumnConstraints();
        keyConstraints.setPercentWidth(25);
        ColumnConstraints textConstraints = new ColumnConstraints();
        textConstraints.setPercentWidth(75);
        grid.getColumnConstraints().addAll(keyConstraints, textConstraints);
        layout.getChildren().add(grid);

        layout.getChildren().add(componentFactory.createSpacer());
        layout.getChildren().add(componentFactory.createSubtitleLabel("Hint(s):", true));
        layout.getChildren().add(componentFactory.createLabel("Try pressing the keys!", true));
        layoutSize = layout.getChildren().size();

        Label wKey = componentFactory.createLabel("W", true);
        Label aKey = componentFactory.createLabel("A", true);
        Label sKey = componentFactory.createLabel("S", true);
        Label dKey = componentFactory.createLabel("D", true);
        GridPane wasdGrid = new GridPane();
        wasdGrid.add(wKey, 1, 0);
        wasdGrid.addRow(1, aKey, sKey, dKey);
        Label wasdLabel = componentFactory.createLabel("Cursor keys (WASD for up, left, down, right respectively).", true);
        grid.addRow(0, wasdGrid, wasdLabel);

        Label spaceKey = componentFactory.createLabel("<space>", true);
        Label spaceLabel = componentFactory.createLabel("Make selection.", true);
        grid.addRow(1, spaceKey, spaceLabel);

        Label eKey = componentFactory.createLabel("E", true);
        Label fKey = componentFactory.createLabel("F", true);
        HBox efGrid = new HBox();
        efGrid.getChildren().addAll(eKey, componentFactory.createFixedDoubleSpacer(), fKey);
        Label efLabel = componentFactory.createLabel("Open chest/start battle respectively (map only).", true);
        grid.addRow(2, efGrid, efLabel);

        Label iKey = componentFactory.createLabel("I", true);
        Label mKey = componentFactory.createLabel("M", true);
        Label cKey = componentFactory.createLabel("C", true);
        HBox imcGrid = new HBox();
        imcGrid.getChildren().addAll(iKey, mKey, cKey);
        Label imcLabel = componentFactory.createLabel("Open/close inventory/minimap/character selector respectively (map only).", true);
        grid.addRow(3, imcGrid, imcLabel);

        Label kKey = componentFactory.createLabel("K", true);
        HBox kGrid = new HBox();
        kGrid.getChildren().addAll(componentFactory.createFixedDoubleSpacer(), kKey, componentFactory.createFixedDoubleSpacer());
        Label kLabel = componentFactory.createLabel("Open/close this menu (anywhere, even on the main menu!).", true);
        grid.addRow(4, kGrid, kLabel);

        buttons = new HashMap<>();
        buttons.put("w", wKey);
        buttons.put("a", aKey);
        buttons.put("s", sKey);
        buttons.put("d", dKey);
        buttons.put(" ", spaceKey);
        buttons.put("e", eKey);
        buttons.put("f", fKey);
        buttons.put("i", iKey);
        buttons.put("m", mKey);
        buttons.put("c", cKey);
        buttons.put("k", kKey);

        for (Label l : new Label[]{wasdLabel, spaceLabel, efLabel, imcLabel, kLabel}) {
            l.setWrapText(true);
            l.setMaxWidth(0.7 * (UIConstants.CANVAS_SIZE - 2 * UIConstants.SINGLE_GAP));
        }

        for (Map.Entry<String, Label> key : buttons.entrySet()) {
            key.getValue().setPrefSize(2 * UIConstants.SINGLE_GAP, 2 * UIConstants.SINGLE_GAP);
            key.getValue().setAlignment(Pos.CENTER);
            key.getValue().setFont(UIConstants.FONT_MONO);
            releaseKey(key.getKey());
        }

        spaceKey.setMaxWidth(6 * UIConstants.SINGLE_GAP); // space edge case
    }

    @Override
    public @NotNull Pane getScene() {
        return pane;
    }

    @Override
    public void toggle(boolean isVisible) {
        this.isVisible = isVisible;

        if (isVisible) {
            for (Map.Entry<String, Label> key : buttons.entrySet()) {
                releaseKey(key.getKey());
            }
        } else {
            while (layout.getChildren().size() > layoutSize) {
                layout.getChildren().remove(layout.getChildren().size() - 1);
            }
        }
    }

    @Override
    public void handle(KeyEvent event) {
        if (!isVisible) {
            return;
        }

        if (event.getEventType().equals(KeyEvent.KEY_PRESSED)) {
            pressKey(event.getText());
        } else if (event.getEventType().equals(KeyEvent.KEY_RELEASED)) {
            releaseKey(event.getText());
        }
    }

    /**
     * Add extra hints at the bottom that disappear when the menu is closed.
     *
     * @param hints the hints to add.
     */
    public void addExtraHints(@NotNull Label... hints) {
        for (Label l : hints) {
            l.setMaxWidth(UIConstants.CANVAS_SIZE - 2 * UIConstants.SINGLE_GAP);
            layout.getChildren().add(l);
        }
    }

    /**
     * Set the style of the key to pressed.
     *
     * @param keyKey the key to be pressed.
     */
    private void pressKey(@NotNull String keyKey) {
        if (buttons.containsKey(keyKey)) {
            buttons.get(keyKey).setTextFill(ColorConstants.ACTIVE);
            buttons.get(keyKey).setBorder(KEY_BORDER_SELECTED);
        }
    }

    /**
     * Set the style of the key to be released.
     *
     * @param keyKey the key to be released.
     */
    private void releaseKey(@NotNull String keyKey) {
        if (buttons.containsKey(keyKey)) {
            buttons.get(keyKey).setTextFill(ColorConstants.WALL);
            buttons.get(keyKey).setBorder(KEY_BORDER_UNSELECTED);
        }
    }
}

Changes to src/main/java/com/mg105/user_interface/LoseMenu.java.

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
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


+
+
-
-
+
+

-
+





-
-
+
+
+
+
+




-
+
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



-
-
+
+




+
+
-
+
+
+
+
+
+
+


package com.mg105.user_interface;

import com.mg105.interface_adapters.InputControllable;
import com.mg105.utils.UIConstants;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import org.jetbrains.annotations.NotNull;

/**
 * LoseMenu is displayed when the player loses the game.
 */
public class LoseMenu implements Toggleable {
    private final @NotNull Scene scene;
public class LoseMenu implements Toggleable, InputControllable {
    private boolean isVisible = false;
    private final @NotNull Runnable replayButton;
    private final @NotNull MainMenu mainMenu;
    private final @NotNull TilePane layout;

    /**
     * Create a new LoseMenu
     *
     * @param replayButton the button that performs the replay action
     * @param replayButton     the button that performs the replay action
     * @param componentFactory the component factory used to create UI components
     * @param mainMenu         the main menu component.
     */
    public LoseMenu(@NotNull ReplayGeneratorButton replayButton) {
        Button generateMapButton = new Button("Replay The Game");
        //Clean Inventory
        generateMapButton.setOnAction(replayButton);
        @NotNull Pane layout = new StackPane();
        layout.getChildren().add(generateMapButton);
        scene = new Scene(layout, 600, 600);
    public LoseMenu(@NotNull Runnable replayButton, @NotNull StandardComponentFactory componentFactory,
                    @NotNull MainMenu mainMenu) {
        this.replayButton = replayButton;
        this.mainMenu = mainMenu;

        layout = new TilePane(Orientation.VERTICAL);
        layout.setBackground(componentFactory.createBackground(false));
        layout.setVgap(UIConstants.SINGLE_GAP);
        layout.setAlignment(Pos.CENTER);

        layout.getChildren().addAll(
            componentFactory.createTitleLabel("Battle Lost", false),
            componentFactory.createLabel("Well that's unfortunate.", false)
        );

        MultiselectGrid options = componentFactory.createMultiselectGrid();
        options.getGrid().setAlignment(Pos.CENTER);
        options.addColumn(componentFactory.createLabel("Now what...", false));
        layout.getChildren().add(options.getGrid());
    }

    @Override
    public @NotNull Scene getScene() {
        return scene;
    public @NotNull Pane getScene() {
        return layout;
    }

    @Override
    public void toggle(boolean isVisible) {
        this.isVisible = isVisible;
    }
        // Does nothing

    @Override
    public void interpret(String key) {
        if (isVisible && key.equals(" ")) {
            mainMenu.lost();
            replayButton.run();
        }
    }
}

Changes to src/main/java/com/mg105/user_interface/MainMenu.java.

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
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


+
+
+
+
+
+
-
-
+
+

-
+





-
-
+
+
+
+
+
+
+
+
+




+
-
+
+

-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



-
-
+
+




+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


package com.mg105.user_interface;

import com.mg105.interface_adapters.InputControllable;
import com.mg105.interface_adapters.Toggler;
import com.mg105.utils.ColorConstants;
import com.mg105.utils.UIConstants;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import org.jetbrains.annotations.NotNull;

/**
 * The main game menu.
 */
public class MainMenu implements Toggleable {
    private final @NotNull Scene scene;
public class MainMenu implements Toggleable, InputControllable {
    private final @NotNull Toggler toggler;
    private final @NotNull Runnable startButton;
    private final @NotNull MultiselectGrid options;
    private boolean isVisible = false;
    private final @NotNull TilePane layout;

    private int playCount = 105;
    private final @NotNull Label playLabel;

    /**
     * Create a new MainMenu.
     *
     * @param toggler          the toggler used to toggle displays.
     * @param startButton the button that starts the game.
     * @param startButton      the button that starts the game.
     * @param componentFactory the component factory used to create UI components
     */
    public MainMenu(@NotNull MapGeneratorButton startButton) {
        Button generateMapButton = new Button("Start Game");
        generateMapButton.setOnAction(startButton);
        @NotNull Pane layout = new StackPane();
        layout.getChildren().add(generateMapButton);
        scene = new Scene(layout, 600, 600);
    public MainMenu(@NotNull Toggler toggler,
                    @NotNull Runnable startButton,
                    @NotNull StandardComponentFactory componentFactory) {
        this.toggler = toggler;
        this.startButton = startButton;

        layout = new TilePane(Orientation.VERTICAL);
        layout.setVgap(UIConstants.SINGLE_GAP);
        layout.setAlignment(Pos.CENTER);
        layout.setBackground(componentFactory.createBackground(false));

        playLabel = componentFactory.createTitleLabel(Integer.toString(playCount), false);
        playLabel.setFont(UIConstants.TITLE_FONT_ALT);

        HBox title = new HBox();
        title.setAlignment(Pos.BASELINE_CENTER);
        title.getChildren().addAll(
            componentFactory.createTitleLabel("Mountain Group  ", false),
            playLabel
        );
        layout.getChildren().add(title);

        options = componentFactory.createMultiselectGrid();
        options.addColumn(
            componentFactory.createLabel("Start Game", false),
            componentFactory.createLabel("Controls", false),
            componentFactory.createLabel("Quit Game", false)
        );
        options.getGrid().setAlignment(Pos.CENTER);
        layout.getChildren().add(options.getGrid());
    }

    @Override
    public @NotNull Scene getScene() {
        return scene;
    public @NotNull Pane getScene() {
        return layout;
    }

    @Override
    public void toggle(boolean isVisible) {
        this.isVisible = isVisible;
    }
        // Does nothing, for now

    @Override
    public void interpret(String key) {
        if (isVisible) {
            switch (key) {
                case "w" -> options.activeUp();
                case "s" -> options.activeDown();
                case " " -> {
                    switch (options.getActive().y) {
                        case 0 -> startButton.run();
                        case 1 -> toggler.toggle(Toggler.ToggleableComponent.CONTROLS);
                        default -> System.exit(0);
                    }
                }
            }
        }
    }

    /**
     * Notify the title screen that the game has been lost.
     */
    public void lost() {
        playLabel.setTextFill(ColorConstants.SNOW_ALT);
        playCount += 1;
        playLabel.setText(Integer.toString(playCount));
    }

    /**
     * Gets the number displayed after 'mountain group'
     *
     * @return the number displayed after 'mountain group'
     */
    public int getPlayCount() {
        return playCount;
    }
}

Changes to src/main/java/com/mg105/user_interface/MapDrawer.java.

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
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



+


+
+
-
-
-
-
+
+
+
+







-







-
+
-


-










-
-
-
-
-
-


-
+


-
-
-
+
+
-
-
-
-
+
+
+


-
-
-
-
+
+
+
+
-
-








-
-
+
+







package com.mg105.user_interface;

import com.mg105.interface_adapters.map.RoomInterpreterInterface;
import com.mg105.interface_adapters.map.RoomState;
import com.mg105.interface_adapters.map.RoomTileType;
import com.mg105.utils.MapConstants;
import com.mg105.utils.Textures;
import com.mg105.utils.UIConstants;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * MapDrawer draws the map as a grid of tiles.
 */
public class MapDrawer implements PropertyChangeListener, Toggleable {
    private final @NotNull RoomInterpreterInterface interpreter;

    private final @NotNull Scene scene;
    private final @NotNull Pane pane;
    private final @NotNull Group group;
    private final @NotNull Map<RoomTileType, Image> tiles;
    private final @NotNull Map<String, Image> playerSprites;
    private final @NotNull Image missingTile;
    private boolean isVisible;

    /**
     * Create an instance of MapDrawer.
     *
     * @param interpreter the room interpreter used to format the data in an acceptable way for this class.
     */
    public MapDrawer(@NotNull RoomInterpreterInterface interpreter) {
        this.interpreter = interpreter;

        group = new Group();
        scene = new Scene(
            group,
            MapConstants.ROOM_SIZE * MapConstants.TILE_SIZE,
            MapConstants.ROOM_SIZE * MapConstants.TILE_SIZE
        );
        isVisible = false;

        missingTile = new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/tiles/missing.png")));
        pane = new Pane();

        tiles = new HashMap<>(6);
        tiles.put(RoomTileType.FLOOR, new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/tiles/floor.png"))));
        tiles.put(RoomTileType.WALL, new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/tiles/wall.png"))));
        tiles.put(RoomTileType.WALL_WITH_FACE, new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/tiles/wall_face.png"))));
        tiles.put(RoomTileType.WALL, Textures.TILE_WALL);
        tiles.put(RoomTileType.WALL_FACE, Textures.TILE_WALL_FACE);
        tiles.put(RoomTileType.EXIT, new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/tiles/exit.png"))));
        tiles.put(RoomTileType.CHEST, new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/tiles/chest.png"))));
        tiles.put(RoomTileType.CHEST_OPEN, new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/tiles/chest_open.png"))));
        tiles.put(RoomTileType.OPPONENT_SET, new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/tiles/battle.png"))));
        tiles.put(RoomTileType.CHEST, Textures.TILE_CHEST);
        tiles.put(RoomTileType.CHEST_OPEN, Textures.TILE_CHEST_OPEN);
        tiles.put(RoomTileType.OPPONENT_SET, Textures.TILE_OPPONENT_SET);

        playerSprites = new HashMap<>(4);
        playerSprites.put("/sprites/A.png", new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/sprites/A.png"))));
        playerSprites.put("/sprites/B.png", new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/sprites/B.png"))));
        playerSprites.put("/sprites/C.png", new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/sprites/C.png"))));
        playerSprites.put("/sprites/D.png", new Image(Objects.requireNonNull(MapDrawer.class.getResourceAsStream("/sprites/D.png"))));
        playerSprites.put("/sprites/A.png", Textures.PLAYER_A);
        playerSprites.put("/sprites/B.png", Textures.PLAYER_B);
        playerSprites.put("/sprites/C.png", Textures.PLAYER_C);
        playerSprites.put("/sprites/D.png", Textures.PLAYER_D);
        // While in theory getResourceAsStream can fail, in practice this will never happen because the images are
        // bundled in the Jar.  If this isn't the case then the NullPointerException is the least of your worries.
    }

    /**
     * Get the scene that will be used to draw to.
     *
     * @return the scene that the MapDrawer will draw to.
     */
    @Override
    public @NotNull Scene getScene() {
        return scene;
    public @NotNull Pane getScene() {
        return pane;
    }

    /**
     * Toggle the visibility of map drawer.  In this case we need to make sure the map drawing is up-to-date.
     *
     * @param isVisible true if the map is now visible, false otherwise.
     */
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






















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







-
+

-
+

+
+
+
+
+
+
+
+
+
-
+

-
-
+
-
-
-
-
+
+
+
-
+

+
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



















-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    }

    /**
     * Redraw the current room.  This method only needs to be called if something has changed in the underlying
     * current room.
     */
    public void updateRoom() {
        RoomTileType[][] room = interpreter.getCurrentRoom();
        RoomState room = interpreter.getCurrentRoom();

        group.getChildren().clear();
        pane.getChildren().clear();

        Rectangle clip = new Rectangle();
        clip.setX(0);
        clip.setY(0);
        clip.setWidth(UIConstants.CANVAS_SIZE);
        clip.setHeight(UIConstants.CANVAS_SIZE);
        pane.setClip(clip);

        final int offsetLayerOffset = -MapConstants.TILE_SIZE / 4;

        for (int y = 0; y < MapConstants.ROOM_SIZE; y++) {
        for (int y = 0; y < MapConstants.ROOM_SIZE + 1; y++) {
            for (int x = 0; x < MapConstants.ROOM_SIZE; x++) {
                ImageView tile = new ImageView(tiles.getOrDefault(room[y][x], missingTile));

                pane.getChildren().add(makeTile(
                tile.setPreserveRatio(true);
                tile.setX(x * MapConstants.TILE_SIZE);
                tile.setY(y * MapConstants.TILE_SIZE);
                tile.setFitHeight(MapConstants.TILE_SIZE);
                    x * MapConstants.TILE_SIZE,
                    y * MapConstants.TILE_SIZE,
                    Textures.TILE_FLOOR
                tile.setFitWidth(MapConstants.TILE_SIZE);
                ));

                if (room.getOffsetLayer()[y][x] != null) {
                group.getChildren().add(tile);
            }
        }

                    pane.getChildren().add(makeTile(
                        x * MapConstants.TILE_SIZE,
                        y * MapConstants.TILE_SIZE + offsetLayerOffset,
                        tiles.getOrDefault(room.getOffsetLayer()[y][x], Textures.MISSING)
                    ));
                }

                if (room.getBaseLayer()[y][x] != null) {
        Point player = interpreter.getPlayer();
        ImageView playerTile = new ImageView(playerSprites.getOrDefault(interpreter.getCharacterSprite(), missingTile));
        playerTile.setPreserveRatio(true);
        playerTile.setX(player.x * MapConstants.TILE_SIZE);
        playerTile.setY(player.y * MapConstants.TILE_SIZE);
        playerTile.setFitHeight(MapConstants.TILE_SIZE);
        playerTile.setFitWidth(MapConstants.TILE_SIZE);
        group.getChildren().add(playerTile);
                    pane.getChildren().add(makeTile(
                        x * MapConstants.TILE_SIZE,
                        y * MapConstants.TILE_SIZE,
                        tiles.getOrDefault(room.getBaseLayer()[y][x], Textures.MISSING)
                    ));
                }

                if (room.getPlayerPosition().equals(new Point(x, y))) {
                    pane.getChildren().add(makeTile(
                        room.getPlayerPosition().x * MapConstants.TILE_SIZE,
                        room.getPlayerPosition().y * MapConstants.TILE_SIZE,
                        playerSprites.getOrDefault(room.getPlayerSprite(), Textures.MISSING)
                    ));
                }
            }
        }
    }

    /**
     * Update the current room based on evt.
     * <p>
     * Note that none of the properties of evt are used.
     *
     * @param evt A PropertyChangeEvent object describing the event source
     *            and the property that has changed.
     */
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (!isVisible) {
            // As per the specification of Toggleable, we do nothing if we are not visible.
            return;
        }

        updateRoom();
    }
}

    /**
     * Create a new tile appropriate given the size of the scene placed at a certain set of coordinates
     *
     * @param x     the x position of the tile.
     * @param y     the y position of the tile.
     * @param image the image used for the tile.
     *
     * @return the tile
     */
    private @NotNull ImageView makeTile(int x, int y, @NotNull Image image) {
        ImageView tile = new ImageView(image);

        tile.setPreserveRatio(true);
        tile.setX(x);
        tile.setY(y);
        tile.setFitHeight(MapConstants.TILE_SIZE);
        tile.setFitWidth(MapConstants.TILE_SIZE);

        return tile;
    }
}

Changes to src/main/java/com/mg105/user_interface/MapGeneratorButton.java.

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
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




-
-





-
+

















-
-


-
+




package com.mg105.user_interface;

import com.mg105.interface_adapters.Toggler;
import com.mg105.interface_adapters.map.MapGeneratorInterpreterInterface;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import org.jetbrains.annotations.NotNull;

/**
 * MapGeneratorButton acts as the event handler for the actual JavaFX button that will generate a map.
 */
public class MapGeneratorButton implements EventHandler<ActionEvent> {
public class MapGeneratorButton implements Runnable {
    private final @NotNull MapGeneratorInterpreterInterface interpreter;
    private final @NotNull Toggler toggler;


    /**
     * Create a new MapGeneratorButton.
     *
     * @param interpreter the interpreter for the map generator button
     * @param toggler     the toggler used to close the user interface once pressed.
     */
    public MapGeneratorButton(@NotNull MapGeneratorInterpreterInterface interpreter, @NotNull Toggler toggler) {
        this.interpreter = interpreter;
        this.toggler = toggler;
    }

    /**
     * This method is called when the button is pressed. It passes control to the appropriate interpreter.
     *
     * @param event the event which occurred
     */
    @Override
    public void handle(ActionEvent event) {
    public void run() {
        interpreter.generateMap();
        toggler.toggle(Toggler.ToggleableComponent.MAIN_MENU);
    }
}

Changes to src/main/java/com/mg105/user_interface/MinimapDrawer.java.

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
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




-
-
-
+
+
+










-
+
-
-
-
-

-
+
-









-
+
-
-








-
-
+
+














-
+


-
-
+
+

-
+
+
+
+
+
+
+
+









-
-
+
+










-
-
+
+














-
+

-
+









-
+

-
+









-
+

-
+









-
+

-
+







package com.mg105.user_interface;

import com.mg105.interface_adapters.map.MinimapInterpreterInterface;
import com.mg105.interface_adapters.map.MinimapRoomState;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import com.mg105.utils.ColorConstants;
import com.mg105.utils.UIConstants;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import org.jetbrains.annotations.NotNull;

import java.awt.*;

/**
 * Draw the Minimap.
 */
public class MinimapDrawer implements Toggleable {
    private static final int CANVAS_SIZE = 400;

    private static final @NotNull Color PATH_COLOR = Color.rgb(135, 160, 227);
    private static final @NotNull Color ROOM_COLOR = Color.rgb(205, 220, 255);
    private static final @NotNull Color CURRENT_ROOM_COLOR = Color.rgb(200, 150, 61);
    private static final @NotNull Color BACKGROUND_COLOR = Color.rgb(38, 44, 68);
    private final @NotNull MinimapInterpreterInterface interpreter;
    private final @NotNull Scene scene;
    private final @NotNull Pane pane;
    private final @NotNull Group group;

    /**
     * Create a new MinimapDrawer.
     *
     * @param interpreter the MinimapInterpreter that will process room change data.
     */
    public MinimapDrawer(@NotNull MinimapInterpreterInterface interpreter) {
        this.interpreter = interpreter;

        group = new Group();
        pane = new Pane();
        scene = new Scene(group, CANVAS_SIZE, CANVAS_SIZE);
        scene.setFill(BACKGROUND_COLOR);
    }

    /**
     * Get the minimap scene.
     *
     * @return the minimap scene.
     */
    @Override
    public @NotNull Scene getScene() {
        return scene;
    public @NotNull Pane getScene() {
        return pane;
    }

    /**
     * Toggle the minimap.
     *
     * @param isVisible true if the Toggleable is now visible, false otherwise.  If false the Toggleable is expected
     *                  to do nothing on ANY user inputs.
     */
    @Override
    public void toggle(boolean isVisible) {
        if (isVisible) {
            MinimapRoomState[][] map = interpreter.getMapSoFar();
            Point currentPosition = interpreter.getCurrentPosition();

            final int cellDimension = CANVAS_SIZE / Math.max(map.length, map[0].length);
            final int cellDimension = UIConstants.CANVAS_SIZE / Math.max(map.length, map[0].length);
            final int innerCellPadding = cellDimension / 6;

            final int topPadding = (CANVAS_SIZE - map.length * cellDimension) / 2;
            final int leftPadding = (CANVAS_SIZE - map[0].length * cellDimension) / 2;
            final int topPadding = (UIConstants.CANVAS_SIZE - map.length * cellDimension) / 2;
            final int leftPadding = (UIConstants.CANVAS_SIZE - map[0].length * cellDimension) / 2;

            group.getChildren().clear();
            pane.getChildren().clear();

            Rectangle background = new Rectangle();
            background.setWidth(UIConstants.CANVAS_SIZE);
            background.setHeight(UIConstants.CANVAS_SIZE);
            background.setFill(ColorConstants.WALL);
            pane.getChildren().add(background);

            for (int y = 0; y < map.length; y++) {
                for (int x = 0; x < map[y].length; x++) {
                    // Shading for the current room
                    if (currentPosition.x == x && currentPosition.y == y) {
                        Rectangle r = new Rectangle();
                        r.setX(leftPadding + x * cellDimension);
                        r.setY(topPadding + y * cellDimension);
                        r.setWidth(cellDimension);
                        r.setHeight(cellDimension);
                        r.setFill(CURRENT_ROOM_COLOR);
                        group.getChildren().add(r);
                        r.setFill(ColorConstants.ACTIVE);
                        pane.getChildren().add(r);
                    }

                    // Rectangle for each room
                    if (map[y][x] == MinimapRoomState.EXPLORED) {
                        // First we draw the middle square
                        Rectangle r = new Rectangle();
                        r.setX(leftPadding + innerCellPadding + x * cellDimension);
                        r.setY(topPadding + innerCellPadding + y * cellDimension);
                        r.setWidth(cellDimension - 2 * innerCellPadding);
                        r.setHeight(cellDimension - 2 * innerCellPadding);
                        r.setFill(ROOM_COLOR);
                        group.getChildren().add(r);
                        r.setFill(ColorConstants.SNOW);
                        pane.getChildren().add(r);

                        // Now we draw the lines that sick out
                        final int xMidpoint = leftPadding + x * cellDimension + cellDimension / 2;
                        final int yMidpoint = topPadding + y * cellDimension + cellDimension / 2;
                        final int strokeWidth = cellDimension / 10;
                        final int strokeWidthCorrection = strokeWidth / 2;

                        // Line coming out the top
                        if (y > 0 && map[y - 1][x] != null) {
                            Line l = new Line();
                            l.setStartX(xMidpoint);
                            l.setEndX(xMidpoint);
                            l.setStartY(topPadding + y * cellDimension + innerCellPadding - strokeWidthCorrection);
                            l.setEndY(topPadding + y * cellDimension + strokeWidthCorrection);
                            l.setStroke(PATH_COLOR);
                            l.setStroke(ColorConstants.SNOW_ALT);
                            l.setStrokeWidth(strokeWidth);
                            group.getChildren().add(l);
                            pane.getChildren().add(l);
                        }

                        // Line coming out the bottom
                        if (y < map.length - 1 && map[y + 1][x] != null) {
                            Line l = new Line();
                            l.setStartX(xMidpoint);
                            l.setEndX(xMidpoint);
                            l.setStartY(topPadding + y * cellDimension + cellDimension - innerCellPadding + strokeWidthCorrection);
                            l.setEndY(topPadding + y * cellDimension + cellDimension - strokeWidthCorrection);
                            l.setStroke(PATH_COLOR);
                            l.setStroke(ColorConstants.SNOW_ALT);
                            l.setStrokeWidth(strokeWidth);
                            group.getChildren().add(l);
                            pane.getChildren().add(l);
                        }

                        // Line coming out the left
                        if (x > 0 && map[y][x - 1] != null) {
                            Line l = new Line();
                            l.setStartX(leftPadding + x * cellDimension + innerCellPadding - strokeWidthCorrection);
                            l.setEndX(leftPadding + x * cellDimension + strokeWidthCorrection);
                            l.setStartY(yMidpoint);
                            l.setEndY(yMidpoint);
                            l.setStroke(PATH_COLOR);
                            l.setStroke(ColorConstants.SNOW_ALT);
                            l.setStrokeWidth(strokeWidth);
                            group.getChildren().add(l);
                            pane.getChildren().add(l);
                        }

                        // Line coming out the right
                        if (x < map[0].length - 1 && map[y][x + 1] != null) {
                            Line l = new Line();
                            l.setStartX(leftPadding + x * cellDimension + cellDimension - innerCellPadding + strokeWidthCorrection);
                            l.setEndX(leftPadding + x * cellDimension + cellDimension - strokeWidthCorrection);
                            l.setStartY(yMidpoint);
                            l.setEndY(yMidpoint);
                            l.setStroke(PATH_COLOR);
                            l.setStroke(ColorConstants.SNOW_ALT);
                            l.setStrokeWidth(strokeWidth);
                            group.getChildren().add(l);
                            pane.getChildren().add(l);
                        }
                    }
                }
            }
        }
    }
}

Added src/main/java/com/mg105/user_interface/MultiselectGrid.java.











































































































































































































































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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.user_interface;

import javafx.scene.Parent;
import javafx.scene.layout.GridPane;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Multiselect grid represents a grid of keyboard controllable virtual buttons.
 * <p>
 * One option in each column can be selected, and there is only one active item.
 */
public class MultiselectGrid {
    private final @NotNull StandardComponentFactory componentFactory;
    private final @NotNull GridPane grid;
    private final @NotNull List<List<Parent>> columns;
    private final @NotNull List<Integer> selected;
    private @NotNull Point active;

    /**
     * Create a new MultiselectGrid.
     *
     * @param componentFactory the component factory used to create components
     */
    public MultiselectGrid(@NotNull StandardComponentFactory componentFactory) {
        this.componentFactory = componentFactory;
        grid = new GridPane();
        columns = new ArrayList<>();
        selected = new ArrayList<>();
        active = new Point();
    }

    /**
     * Get the actual displayable grid.
     *
     * @return the grid.
     */
    public @NotNull GridPane getGrid() {
        return grid;
    }

    /**
     * Update the visual representation of the grid.
     */
    private void updateGrid() {
        grid.getChildren().clear();

        for (int x = 0; x < columns.size(); x++) {
            for (int y = 0; y < columns.get(x).size(); y++) {
                grid.add(columns.get(x).get(y), x * 3 + 1, y);
            }
        }

        for (int x = 0; x < selected.size(); x++) {
            if (selected.get(x) != null) {
                grid.add(componentFactory.createActiveLabel(" [ "), x * 3, selected.get(x));
                grid.add(componentFactory.createActiveLabel(" ] "), x * 3 + 2, selected.get(x));
            }
        }

        grid.add(componentFactory.createActiveLabel(" > "), active.x * 3, active.y);
        grid.add(componentFactory.createActiveLabel(" < "), active.x * 3 + 2, active.y);
    }

    /**
     * Add a column to the end of the grid.
     *
     * @param items the contents of the column.
     */
    public void addColumn(@NotNull Parent... items) {
        columns.add(new ArrayList<>(List.of(items)));
        updateGrid();
    }

    /**
     * Add an item to the last column.
     *
     * @param item the item to add.
     */
    public void addToColumn(@NotNull Parent item) {
        assert columns.size() > 0;
        columns.get(columns.size() - 1).add(item);
        updateGrid();
    }

    /**
     * Remove the last column fromm the grid.
     */
    public void popColumn() {
        assert columns.size() > 0;
        columns.remove(columns.size() - 1);
        if (active.x == columns.size()) {
            activeLeft();
        }
        while (selected.size() >= columns.size()) {
            selected.remove(selected.size() - 1);
        }
        updateGrid();
    }

    /**
     * Set next as the next active point.
     *
     * @param next the next active position.
     *
     * @return true if the active position changed, false otherwise.
     */
    private boolean setActive(@NotNull Point next) {
        try {
            if (columns.get(next.x).get(next.y) != null && !active.equals(next)) {
                active = new Point(next);
                updateGrid();
                return true;
            } else {
                return false;
            }
        } catch (IndexOutOfBoundsException e) {
            return false;
        }
    }

    /**
     * Get the active location.
     *
     * @return the active location.
     */
    public @NotNull Point getActive() {
        return new Point(active);
    }

    /**
     * Move the active location up by 1 unit.
     *
     * @return true if the active position changed, false otherwise.
     */
    public boolean activeUp() {
        Point next = new Point(active);
        next.y -= 1;
        return setActive(next);
    }

    /**
     * Move the active location down by 1 unit.
     *
     * @return true if the active position changed, false otherwise.
     */
    public boolean activeDown() {
        Point next = new Point(active);
        next.y += 1;
        return setActive(next);
    }

    /**
     * Sets the active point to start or a point above it.
     *
     * @param start a starting point.
     *
     * @return true if the active position changed, false otherwise.
     */
    private boolean setClosestActive(Point start) {
        if (setActive(start)) {
            return true;
        } else if (start.y >= 0) {
            start.y -= 1;
            return setClosestActive(start);
        } else {
            return false;
        }
    }

    /**
     * Move the active location left by 1 unit.
     *
     * @return true if the active position changed, false otherwise.
     */
    public boolean activeLeft() {
        Point next = new Point(active);
        next.x -= 1;
        return setClosestActive(next);
    }

    /**
     * Move the active location right by 1 unit.
     *
     * @return true if the active position changed, false otherwise.
     */
    public boolean activeRight() {
        Point next = new Point(active);
        next.x += 1;
        return setClosestActive(next);
    }

    /**
     * Set the current selected as active.
     */
    public void selectActive() {
        while (selected.size() < columns.size()) {
            selected.add(null);
        }

        selected.set(active.x, active.y);
    }

    /**
     * Get the selected elements so far.
     *
     * @return the selected elements so far.
     */
    public @NotNull List<Integer> getSelected() {
        return new ArrayList<>(selected);
    }

    /**
     * Reset the multiselect grid.
     */
    public void reset() {
        active.x = 0;
        active.y = 0;
        selected.clear();
        columns.clear();
    }

    /**
     * Gets if this grid is empty.
     *
     * @return if the mutiselect grid contains no columns.
     */
    public boolean isEmpty() {
        return columns.isEmpty();
    }
}

Changes to src/main/java/com/mg105/user_interface/ReplayGeneratorButton.java.

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
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





-
-






-
+

















-
-
-
-
-

-
+


+

-
+
-
package com.mg105.user_interface;

import com.mg105.interface_adapters.ReplayGeneratorInterpreter;
import com.mg105.interface_adapters.Toggler;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import org.jetbrains.annotations.NotNull;


/**
 * ReplayGeneratorButton acts as the event handler for the actual JavaFx button that will replay the game.
 */
public class ReplayGeneratorButton implements EventHandler<javafx.event.ActionEvent> {
public class ReplayGeneratorButton implements Runnable {
    private final @NotNull ReplayGeneratorInterpreter interpreter;
    private final @NotNull Toggler toggler;
    private final @NotNull Toggler.ToggleableComponent componentToToggle;

    /**
     * Create a new ReplayGeneratorButton.
     *
     * @param interpreter       the interpreter for the replay generator button.
     * @param toggler           the toggler used to close the user interface once pressed.
     * @param componentToToggle the component that needs to be toggled
     */
    public ReplayGeneratorButton(@NotNull ReplayGeneratorInterpreter interpreter, @NotNull Toggler toggler, Toggler.@NotNull ToggleableComponent componentToToggle) {
        this.interpreter = interpreter;
        this.toggler = toggler;
        this.componentToToggle = componentToToggle;
    }

    /**
     * this method is called when the button is pressed. It passes control to the appropriate interpreter.
     *
     * @param event the event which occurred
     */
    @Override
    public void handle(ActionEvent event) {
    public void run() {
        interpreter.replayGenerateMap();
        toggler.toggle(componentToToggle);
        toggler.toggle(Toggler.ToggleableComponent.MAIN_MENU);
    }

}
}

Changes to src/main/java/com/mg105/user_interface/SceneController.java.

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
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


+

+
+
+
+
+
+



+

+





-
+

+





+
+


















+
+
+
+
+
+
+
+
+
+
+
+







package com.mg105.user_interface;

import com.mg105.interface_adapters.InputControllable;
import com.mg105.interface_adapters.Toggler;
import com.mg105.utils.UIConstants;
import javafx.animation.AnimationTimer;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.jetbrains.annotations.NotNull;

import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Stack;

/**
 * SceneController acts as the central coordination mechanism for the user interface.
 */
public class SceneController implements Toggler {
public class SceneController implements Toggler, Alerter, InputControllable {
    private final @NotNull Stage primaryStage;
    private final @NotNull StackPane layout;

    private final @NotNull Map<ToggleableComponent, Toggleable> components;
    private final @NotNull ToggleableComponent defaultComponent;

    private final @NotNull Stack<ToggleableComponent> activeComponents;

    private final @NotNull Queue<Parent> alerts;

    /**
     * Create a new scene controller
     *
     * @param primaryStage     the JavaFX stage to draw on.
     * @param components       all the possible components in the system.  Components should contain an entry for each
     *                         item in the ToggleableComponent enum.
     * @param defaultComponent the component that will be shown if there's nothing else to show.
     */
    public SceneController(@NotNull Stage primaryStage,
                           @NotNull Map<ToggleableComponent, Toggleable> components,
                           @NotNull ToggleableComponent defaultComponent) {
        this.primaryStage = primaryStage;
        this.defaultComponent = defaultComponent;
        this.components = components;

        activeComponents = new Stack<>();
        activeComponents.push(defaultComponent);

        layout = new StackPane();
        layout.setPrefSize(UIConstants.CANVAS_SIZE, UIConstants.CANVAS_SIZE);
        layout.setMaxSize(UIConstants.CANVAS_SIZE, UIConstants.CANVAS_SIZE);
        layout.getChildren().add(new Pane());

        primaryStage.setScene(new Scene(
            layout,
            UIConstants.CANVAS_SIZE, UIConstants.CANVAS_SIZE
        ));

        alerts = new LinkedList<>();
    }

    /**
     * Toggle the component. i.e. switch it between invisible and visible.
     *
     * @param component the component to toggle.
     */
53
54
55
56
57
58
59

60









61
62
63
64
65
66
67
68
69
70



71
72
73












































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







+
-
+
+
+
+
+
+
+
+
+










+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
        }

        if (activeComponents.isEmpty()) {
            activeComponents.push(defaultComponent);
        }

        components.get(activeComponents.peek()).toggle(true);

        primaryStage.setScene(components.get(activeComponents.peek()).getScene());
        Pane nextComponent = components.get(activeComponents.peek()).getScene();
        layout.getChildren().set(0, nextComponent);

        // force alignment to top left and try to force appropriate sizing
        nextComponent.setTranslateX(-nextComponent.getLayoutX());
        nextComponent.setTranslateY(-nextComponent.getLayoutY());
        nextComponent.setPrefSize(UIConstants.CANVAS_SIZE, UIConstants.CANVAS_SIZE);
        nextComponent.setMaxSize(UIConstants.CANVAS_SIZE, UIConstants.CANVAS_SIZE);

        primaryStage.setTitle("Mountain Group 105: " + activeComponents.peek());
    }

    /**
     * Get the current visible component.
     *
     * @return the current visible component.
     */
    @Override
    public @NotNull ToggleableComponent getCurrentComponent() {
        if (layout.getChildren().size() > 1) {
            return ToggleableComponent.ALERT;
        } else {
        return activeComponents.peek();
    }
}
            return activeComponents.peek();
        }
    }

    /**
     * Display the alert on top of the current screen.
     *
     * @param body the alert.
     */
    @Override
    public void displayAlert(@NotNull Parent body) {
        alerts.add(body);

        if (layout.getChildren().size() == 1) {
            layout.getChildren().add(alerts.remove());
        }
    }

    /**
     * Interpret a key.  Used for the sole purpose of dismissing alerts.
     *
     * @param key the key this component should process.
     */
    @Override
    public void interpret(String key) {
        layout.getChildren().remove(1);

        if (!alerts.isEmpty()) {
            new AnimationTimer() {
                private long last = 0;

                @Override
                public void handle(long now) {
                    if (last == 0) {
                        last = now;
                    } else if (now - last > 170 * 1e6) {
                        layout.getChildren().add(alerts.remove());
                        stop();
                    }
                }
            }.start();
        }
    }
}

Added src/main/java/com/mg105/user_interface/StandardComponentFactory.java.














































































































































































































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
200
201
202
203
204
205
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.user_interface;

import com.mg105.utils.ColorConstants;
import com.mg105.utils.UIConstants;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import org.jetbrains.annotations.NotNull;

/**
 * The StandardComponentFactory is used to delegate creation of JavaFX UI components so that they all look coherent.
 */
public class StandardComponentFactory {
    private final @NotNull Alerter alerter;

    /**
     * Create a StandardComponentFactory.
     *
     * @param alerter the interface used to alert.
     */
    public StandardComponentFactory(@NotNull Alerter alerter) {
        this.alerter = alerter;
    }

    /**
     * Create a new background.
     *
     * @param isLight if the background should be light (otherwise it is dark).
     *
     * @return the new background.
     */
    public @NotNull Background createBackground(boolean isLight) {
        Color backgroundColor = (isLight ? ColorConstants.SNOW : ColorConstants.WALL);
        return new Background(new BackgroundFill(backgroundColor, null, null));
    }

    /**
     * Create a label.
     *
     * @param text    the label text.
     * @param isLight if the background is light (otherwise it is presumed dark).
     *
     * @return a new label.
     */
    public @NotNull Label createLabel(@NotNull String text, boolean isLight) {
        Label l = new Label(text);
        l.setTextFill((isLight ? ColorConstants.WALL : ColorConstants.SNOW));
        l.setFont(UIConstants.FONT_NORMAL);
        return l;
    }

    /**
     * Create an active label.
     *
     * @param text the text of the label.
     *
     * @return a new label.
     */
    public @NotNull Label createActiveLabel(@NotNull String text) {
        Label l = createLabel(text, true);
        l.setTextFill(ColorConstants.ACTIVE);
        return l;
    }

    /**
     * Create a title label.
     *
     * @param text    the label text.
     * @param isLight if the background is light (otherwise it is presumed dark).
     *
     * @return a new label.
     */
    public @NotNull Label createTitleLabel(@NotNull String text, boolean isLight) {
        Label l = createLabel(text, isLight);
        l.setFont(UIConstants.TITLE_FONT);
        return l;
    }

    /**
     * Create a label representing a number.
     *
     * @param num     the label number.
     * @param isLight if the background is light (otherwise it is presumed dark).
     *
     * @return a new label.
     */
    public @NotNull Label createNumberedLabel(int num, boolean isLight) {
        Label l = createLabel((num > 0 ? "+" : "") + Integer.toString(num), isLight);
        if (num > 0) {
            l.setTextFill(Color.GREEN);
        } else if (num < 0) {
            l.setTextFill(Color.RED);
        }
        return l;
    }

    /**
     * Create a subtitle label.
     *
     * @param text    the label text.
     * @param isLight if the background is light (otherwise it is presumed dark).
     *
     * @return a new label.
     */
    public @NotNull Label createSubtitleLabel(@NotNull String text, boolean isLight) {
        Label l = createLabel(text, isLight);
        l.setFont(UIConstants.SUBTITLE_FONT);
        return l;
    }

    /**
     * Create a new multiselect grid.
     *
     * @return a new multiselect grid.
     */
    public @NotNull MultiselectGrid createMultiselectGrid() {
        return new MultiselectGrid(this);
    }

    /**
     * Create a growing spacer.
     *
     * @return a new spacer
     */
    public @NotNull Region createSpacer() {
        Region r = new Region();
        r.setPrefSize(UIConstants.SINGLE_GAP, UIConstants.SINGLE_GAP);
        VBox.setVgrow(r, Priority.ALWAYS);
        HBox.setHgrow(r, Priority.ALWAYS);
        return r;
    }

    /**
     * Create a larger spacer.
     *
     * @return a larger spacer.
     */
    public @NotNull Region createFixedDoubleSpacer() {
        Region r = new Region();
        r.setPrefSize(2 * UIConstants.SINGLE_GAP, 2 * UIConstants.SINGLE_GAP);
        return r;
    }

    /**
     * Create a small spacer.
     *
     * @return a smaller spacer.
     */
    public @NotNull Region createFixedHalfSpacer() {
        Region r = new Region();
        r.setPrefSize((int)(UIConstants.SINGLE_GAP / 2), (int)(UIConstants.SINGLE_GAP / 2));
        return r;
    }

    /**
     * Create an alerting modal dialog.
     *
     * @param text    the text of the alert.
     * @param isLight if the background should be light.
     */
    public void createAlert(@NotNull String text, boolean isLight) {
        DropShadow shadow = new DropShadow();
        shadow.setOffsetY((int)(UIConstants.SINGLE_GAP / 2));
        shadow.setColor(Color.color(0.4, 0.4, 0.5));

        TilePane pane = new TilePane(Orientation.VERTICAL);
        pane.setBackground(createBackground(isLight));
        pane.setAlignment(Pos.CENTER);
        pane.setPadding(createInsets());
        pane.setVgap(UIConstants.SINGLE_GAP);
        pane.setMaxWidth((int)(UIConstants.CANVAS_SIZE / 2));
        pane.setMaxHeight((int)(UIConstants.CANVAS_SIZE / 2));
        pane.setEffect(shadow);
        pane.setBorder(new Border(new BorderStroke(
            (isLight ? ColorConstants.WALL : ColorConstants.SNOW),
            BorderStrokeStyle.SOLID,
            CornerRadii.EMPTY,
            new BorderWidths((int)(UIConstants.SINGLE_GAP / 4))
        )));

        Label message = createLabel(text, isLight);
        message.setWrapText(true);
        pane.getChildren().add(message);

        MultiselectGrid okButton = createMultiselectGrid();
        okButton.getGrid().setAlignment(Pos.CENTER);
        okButton.addColumn(createLabel("Ok", isLight));
        pane.getChildren().add(okButton.getGrid());

        alerter.displayAlert(pane);
    }

    /**
     * Create standard insets.
     *
     * @return standard insets.
     */
    public @NotNull Insets createInsets() {
        return new Insets(UIConstants.SINGLE_GAP);
    }
}

Changes to src/main/java/com/mg105/user_interface/Toggleable.java.

1
2
3

4
5
6
7
8
9
10
11
12
13
14
15

16
17
18
19
20
21
22
1
2

3
4
5
6
7
8
9
10
11
12
13
14

15
16
17
18
19
20
21
22


-
+











-
+







package com.mg105.user_interface;

import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import org.jetbrains.annotations.NotNull;

/**
 * A user interface component that can be toggled on or off depending on its visibility.
 */
public interface Toggleable {
    /**
     * Get the scene of this toggleable object.  It is this scene that will be displayed.
     *
     * @return the scene to be displayed.
     */
    @NotNull Scene getScene();
    @NotNull Pane getScene();

    /**
     * Set the visibility of this component.
     *
     * @param isVisible true if the Toggleable is now visible, false otherwise.  If false the Toggleable is expected
     *                  to do nothing on ANY user inputs.
     */

Deleted src/main/java/com/mg105/user_interface/TutorialTextDisplay.java.

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























































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.user_interface;

import com.mg105.interface_adapters.tutorial.TutorialTextController;
import com.mg105.utils.TutorialTexts;
import javafx.scene.control.Label;
import javafx.scene.text.Font;

/**
 * Returns the actual text that is displayed to the player
 */
public class TutorialTextDisplay {
    private final TutorialTextController tutorialControl = new TutorialTextController(false);

    /**
     * Constructor for a new TutorialTextDisplay ui
     */
    public TutorialTextDisplay() {
    }

    /**
     * Check if specific action has been performed
     *
     * @param displayedText the String representing the current phase
     * @return the actual text displayed to the user
     */
    public String showBottomText(String displayedText) {
        String tutorialText;
        int phase_idx = tutorialControl.allPhases().indexOf(displayedText);
        tutorialText = TutorialTexts.PHASES_TEXT.get(phase_idx);
        return tutorialText;

    }

    /**
     * Return a label that will show at the bottom of the screen.
     *
     * @return the label
     */
    public Label tutorialLabel() {
        Label bottomText = new Label();
        bottomText.setFont(Font.font(TutorialTexts.TEXT_SIZE));
        return bottomText;
    }

    /**
     * Get a tutorial controller which is used to switch text
     *
     * @return instance of the controller
     */
    public TutorialTextController getController() {
        return this.tutorialControl;
    }
}


Deleted src/main/java/com/mg105/user_interface/TutorialTextWindow.java.

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


































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.user_interface;

import com.mg105.interface_adapters.tutorial.TutorialTextController;
import com.mg105.utils.TutorialTexts;
import javafx.animation.AnimationTimer;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jetbrains.annotations.NotNull;

/**
 * Create a window for the tutorial scenes
 */
public class TutorialTextWindow implements Toggleable {
    final TutorialTextDisplay tutorialDisplay;
    final Pane tutorialPane = new Pane();
    final Label bottomText = new Label();
    final Label helpText;
    final TutorialTextController textController;
    final Scene tutorialScene = new Scene(tutorialPane, TutorialTexts.HELPER_PANE_X, TutorialTexts.HELPER_PANE_Y);

    /**
     * Window for the tutorial
     *
     * @param textController  the controller for the tutorial
     * @param tutorialDisplay the ui that displays the tutorial
     */
    public TutorialTextWindow(TutorialTextController textController, @NotNull TutorialTextDisplay tutorialDisplay) {
        this.textController = textController;
        this.tutorialDisplay = tutorialDisplay;

        bottomText.setFont(Font.font(TutorialTexts.TEXT_SIZE));

        helpText = tutorialDisplay.tutorialLabel();

        helpText.setFont(Font.font(TutorialTexts.TEXT_SIZE));
        helpText.setBackground(new Background(new BackgroundFill(Color.rgb(255, 215, 0, 1),
            new CornerRadii(80.0), new Insets(-100.0))));

        tutorialPane.getChildren().add(bottomText);
        tutorialPane.getChildren().add(helpText);

        AnimationTimer timer = new TutorialTimer();
        timer.start();
    }

    /**
     * Get the scene of this toggleable object.  It is this scene that will be displayed.
     *
     * @return the scene to be displayed.
     */
    @Override
    public @NotNull Scene getScene() {
        return tutorialScene;
    }

    /**
     * Set the visibility of this component.
     *
     * @param isVisible true if the Toggleable is now visible, false otherwise.  If false the Toggleable is expected
     *                  to do nothing on ANY user inputs.
     */
    @Override
    public void toggle(boolean isVisible) {
    }

    private class TutorialTimer extends AnimationTimer {
        private long prevTime = 0;
        private double helpTimer = TutorialTexts.HELP_TIME;

        /**
         * This method needs to be overridden by extending classes. It is going to
         * be called in every frame while the {@code AnimationTimer} is active.
         *
         * @param now The timestamp of the current frame given in nanoseconds. This
         *            value will be the same for all {@code AnimationTimers} called
         *            during one frame.
         */
        @Override
        public void handle(long now) {
            long timeChange = now - prevTime;

            // 1e9 is 1 second
            if (timeChange > TutorialTexts.TEXT_DURATION1 * 1e9 & textController.changeText()) {
                prevTime = now;
                tutorialDisplay.getController().nextPhase();
                String tutorialText = tutorialDisplay.getController().bottomText();
                bottomText.setText(tutorialDisplay.showBottomText(tutorialText));
            }

            if (textController.getShowControls() & textController.isComplete()) {
                helpText.setText(TutorialTexts.CONTROLS);
                helpTimer--;
                if (helpTimer < 1) {
                    textController.setShowControls(false);
                    helpTimer = TutorialTexts.HELP_TIME;
                }
            } else if (textController.getShowControls()) {
                if (textController.getActionPerformed(TutorialTexts.MOVED)) {
                    helpText.setText(TutorialTexts.DID_NOT_MOVE);
                } else if (textController.getActionPerformed(TutorialTexts.USED_ITEM)) {
                    helpText.setText(TutorialTexts.DID_NOT_OPEN_CHEST);
                } else {
                    helpText.setText(TutorialTexts.DID_NOT_BATTLE);
                }

                helpTimer--;
                if (helpTimer < 1) {
                    textController.setShowControls(false);
                    helpTimer = TutorialTexts.HELP_TIME;
                }

            } else {
                helpText.setText("");
            }

        }
    }
}





Changes to src/main/java/com/mg105/user_interface/WalkingMenu.java.

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
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


+
+


-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+





-
+

-
-
+
+
-
-
-
-
+
+





-
+
+
+

-
+
+

-
+
-

-
-
+
-
-
+
-
-
-
-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-

-
-
+
-
-
-
+

-
-
-
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+








-
-
+
+












+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

package com.mg105.user_interface;

import com.mg105.interface_adapters.InputControllable;
import com.mg105.interface_adapters.Toggler;
import com.mg105.interface_adapters.WalkVisController;
import com.mg105.utils.PartyConstants;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import com.mg105.utils.Textures;
import com.mg105.utils.UIConstants;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.image.ImageView;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.TilePane;
import org.jetbrains.annotations.NotNull;

/**
 * This class uses JavaFX and is displayed when the user wants to change the walking character sprite.
 */
public class WalkingMenu implements EventHandler<ActionEvent>, Toggleable {
public class WalkingMenu implements InputControllable, Toggleable {
    private final WalkVisController controller;
    private final RadioButton radA;
    private final RadioButton radB;
    private final TilePane layout;
    private final @NotNull ImageView nextSelected;
    private final RadioButton radC;
    private final RadioButton radD;
    private final Button select;
    private final Scene scene;
    private final @NotNull MultiselectGrid options;
    private final @NotNull Toggler toggler;

    /**
     * Creates a new WalkingMenu with reference to a controller class.
     * Sets up UI elements.
     *
     * @param controller the WalkVisController to be referred to.
     * @param controller       the WalkVisController to be referred to.
     * @param toggler          the toggler used to toggle this menu.
     * @param componentFactory the component factory used to create UI components
     */
    public WalkingMenu(WalkVisController controller) {
    public WalkingMenu(@NotNull WalkVisController controller, @NotNull Toggler toggler,
                       @NotNull StandardComponentFactory componentFactory) {
        this.controller = controller;

        this.toggler = toggler;
        StackPane layout = new StackPane();

        ToggleGroup group = new ToggleGroup();

        layout = new TilePane(Orientation.VERTICAL);
        VBox buttons = new VBox();

        layout.setVgap(UIConstants.SINGLE_GAP);
        radA = new RadioButton(PartyConstants.ALL_PARTY_MEMBER_NAMES[0]);
        radA.setToggleGroup(group);
        radA.setSelected(true);

        layout.setAlignment(Pos.CENTER);
        radB = new RadioButton(PartyConstants.ALL_PARTY_MEMBER_NAMES[1]);
        radB.setToggleGroup(group);
        radB.setSelected(false);

        layout.setBackground(componentFactory.createBackground(true));
        radC = new RadioButton(PartyConstants.ALL_PARTY_MEMBER_NAMES[2]);
        radC.setToggleGroup(group);
        radC.setSelected(false);

        radD = new RadioButton(PartyConstants.ALL_PARTY_MEMBER_NAMES[3]);
        radD.setToggleGroup(group);
        radD.setSelected(false);

        select = new Button("Confirm");
        select.setOnAction(this);

        Label exitLbl = new Label("Press space bar to exit.");

        nextSelected = new ImageView(Textures.PLAYER_A);
        buttons.getChildren().addAll(radA, radB, radC, radD, select, exitLbl);
        buttons.setAlignment(Pos.CENTER);
        layout.getChildren().addAll(buttons);
        layout.getChildren().add(nextSelected);

        scene = new Scene(layout, 300, 300);
    }

        options = componentFactory.createMultiselectGrid();
    /**
     * Handles button events.
     * Pressing the select button updates the WalkingCharacter to represent the desired walking character sprite.
     *
     * @param event the event which occurred.
     */
    @Override
    public void handle(ActionEvent event) {
        options.addColumn();
        Object source = event.getSource();
        if (source.equals(select)) {
            if (radA.isSelected()) {
                controller.changePlayerSprite("A");
            } else if (radB.isSelected()) {
                controller.changePlayerSprite("B");
            } else if (radC.isSelected()) {
                controller.changePlayerSprite("C");
            } else if (radD.isSelected()) {
                controller.changePlayerSprite("D");
            }
        for (String name : PartyConstants.ALL_PARTY_MEMBER_NAMES) {
            options.addToColumn(componentFactory.createLabel(name, true));
        }
        }
        layout.getChildren().add(options.getGrid());
    }

    /**
     * Get the scene of this toggleable object.  It is this scene that will be displayed.
     *
     * @return the scene to be displayed.
     */
    @Override
    public @NotNull Scene getScene() {
        return this.scene;
    public @NotNull Pane getScene() {
        return layout;
    }

    /**
     * Set the visibility of this component.
     *
     * @param isVisible true if the Toggleable is now visible, false otherwise.  If false the Toggleable is expected
     *                  to do nothing on ANY user inputs.
     */
    @Override
    public void toggle(boolean isVisible) {
        //The menu should ideally remain the same.
    }

    @Override
    public void interpret(String key) {
        switch (key) {
            case "w" -> {
                options.activeUp();
                updateImage(options.getActive().y);
            }
            case "s" -> {
                options.activeDown();
                updateImage(options.getActive().y);
            }
            case " " -> {
                controller.changePlayerSprite(Character.toString('A' + options.getActive().y));
                toggler.toggle(Toggler.ToggleableComponent.WALK_MENU);
            }
        }
    }

    /**
     * Update the visual image.
     *
     * @param id the id of the new image.
     */
    private void updateImage(int id) {
        nextSelected.setImage(switch (id) {
            case 0 -> Textures.PLAYER_A;
            case 1 -> Textures.PLAYER_B;
            case 2 -> Textures.PLAYER_C;
            case 3 -> Textures.PLAYER_D;
            default -> Textures.MISSING;
        });
    }
}

Changes to src/main/java/com/mg105/user_interface/WinMenu.java.

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
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


+
-
-
-
+
+
+
-


-
+
-





-
-
+
+
+
+
+
+




-
+
+
+

-
-
-
+
+
+
+
+
-
-
+
+
+
+

-
-
+

-
-
-
+
+
+
+
+
+
+
+
+
-








-
-
+
+









+
-
+
+
+
+
+
+
+
+
+
+
+


package com.mg105.user_interface;

import com.mg105.interface_adapters.InputControllable;
import com.mg105.utils.TutorialTexts;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import com.mg105.utils.UIConstants;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import javafx.scene.text.Font;
import org.jetbrains.annotations.NotNull;

/**
 * A menu that is shown to the player after they have reached the final room
 */
public class WinMenu implements Toggleable {
    private final @NotNull Scene scene;
public class WinMenu implements Toggleable, InputControllable {
    private boolean isVisible = false;
    private final @NotNull Runnable replayButton;
    private final @NotNull TilePane layout;
    private final @NotNull MainMenu mainMenu;
    private final @NotNull Label message;

    /**
     * Shows a button that congratulates you and lets you replay the game
     *
     * @param ReplayButton the button responsible for replaying the game
     * @param replayButton     the button responsible for replaying the game
     * @param componentFactory the component factory used to create UI components
     * @param mainMenu         the main menu component.
     */
    public WinMenu(@NotNull ReplayGeneratorButton ReplayButton) {
        Label winLbl = new Label();
        winLbl.setText("Congratulations on reaching the final room!!");
    public WinMenu(@NotNull Runnable replayButton, @NotNull StandardComponentFactory componentFactory,
                   @NotNull MainMenu mainMenu) {
        this.replayButton = replayButton;
        this.mainMenu = mainMenu;

        winLbl.setFont(Font.font(TutorialTexts.TEXT_SIZE_LARGE));
        StackPane.setAlignment(winLbl, Pos.TOP_CENTER);
        layout = new TilePane(Orientation.VERTICAL);
        layout.setBackground(componentFactory.createBackground(true));
        layout.setVgap(UIConstants.SINGLE_GAP);
        layout.setAlignment(Pos.CENTER);

        Button generateMapButton = new Button("You Won! Replay The Game");
        generateMapButton.setOnAction(ReplayButton);
        message = componentFactory.createLabel("UNSET", true);

        @NotNull Pane layout = new StackPane();
        layout.getChildren().add(generateMapButton);
        layout.getChildren().add(winLbl);
        layout.getChildren().addAll(
            componentFactory.createTitleLabel("Summit Conquered", true),
            message
        );

        MultiselectGrid options = componentFactory.createMultiselectGrid();
        options.addColumn(componentFactory.createLabel("Now what...", true));
        options.getGrid().setAlignment(Pos.CENTER);
        layout.getChildren().add(options.getGrid());
        scene = new Scene(layout, 600, 600);
    }

    /**
     * Get scene of the WinMenu
     *
     * @return the scene that WinMenu is in
     */
    @Override
    public @NotNull Scene getScene() {
        return scene;
    public @NotNull Pane getScene() {
        return layout;
    }

    /**
     * Toggle the WinMenu
     *
     * @param isVisible whether the menu is visible
     */
    @Override
    public void toggle(boolean isVisible) {
        this.isVisible = isVisible;
        // Does nothing, for now

        if (isVisible) {
            message.setText("The venerated Mountain Group " + mainMenu.getPlayCount() + " does it again!");
        }
    }

    @Override
    public void interpret(String key) {
        if (isVisible && key.equals(" ")) {
            replayButton.run();
        }
    }
}

Added src/main/java/com/mg105/utils/ColorConstants.java.



























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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.utils;

import javafx.scene.paint.Color;
import org.jetbrains.annotations.NotNull;

/**
 * The colour palette of the game.
 */
public class ColorConstants {
    /**
     * The colour used for wall (out of bounds) areas.
     */
    public static final @NotNull Color WALL = Color.rgb(38, 44, 68);
    /**
     * The colour used for snow.
     */
    public static final @NotNull Color SNOW = Color.rgb(205, 220, 255);
    /**
     * The accent colour used for snow.
     */
    public static final @NotNull Color SNOW_ALT = Color.rgb(135, 160, 227);
    /**
     * The colour used when something is active.
     */
    public static final @NotNull Color ACTIVE = Color.rgb(200, 150, 61);
}

Added src/main/java/com/mg105/utils/Textures.java.



























































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.utils;

import javafx.scene.image.Image;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;

/**
 * The textures (images) that are used in the game.
 */
public class Textures {
    /**
     * The texture if none is found.
     */
    public static @NotNull Image MISSING = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/tiles/missing.png")));

    /**
     * The floor image.
     */
    public static @NotNull Image TILE_FLOOR = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/tiles/floor.png")));
    /**
     * The wall image.
     */
    public static @NotNull Image TILE_WALL = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/tiles/wall.png")));
    /**
     * The wall's face image.
     */
    public static @NotNull Image TILE_WALL_FACE = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/tiles/wall_face.png")));
    /**
     * The tile used for a closed chest.
     */
    public static @NotNull Image TILE_CHEST = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/tiles/chest.png")));
    /**
     * The tile used for an open chest.
     */
    public static @NotNull Image TILE_CHEST_OPEN = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/tiles/chest_open.png")));
    /**
     * The tile used for an opponent set.
     */
    public static @NotNull Image TILE_OPPONENT_SET = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/tiles/battle.png")));

    /**
     * The tile for player A.
     */
    public static @NotNull Image PLAYER_A = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/sprites/A.png")));
    /**
     * The tile for player B.
     */
    public static @NotNull Image PLAYER_B = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/sprites/B.png")));
    /**
     * The tile for player C.
     */
    public static @NotNull Image PLAYER_C = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/sprites/C.png")));
    /**
     * The tile for player D.
     */
    public static @NotNull Image PLAYER_D = new Image(Objects.requireNonNull(Textures.class.getResourceAsStream("/sprites/D.png")));
}

Deleted src/main/java/com/mg105/utils/TutorialTexts.java.

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





























































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.utils;

import java.util.Arrays;
import java.util.List;

/**
 * Constants for texts related to the tutorial
 */
public class TutorialTexts {
    /**
     * Constant string for move phase
     */
    public static final String MOVED = "moved";
    /**
     * Constant string for attack phase
     */
    public static final String ATTACKED = "attacked";
    /**
     * Constant string for using item phase
     */
    public static final String USED_ITEM = "usedItem";
    /**
     * Constant int for x size of helper pane
     */
    public static final int HELPER_PANE_X = 420;

    /**
     * Constant int for y size of helper pane
     */
    public static final int HELPER_PANE_Y = 100;

    /**
     * Constant int for text duration per phase
     */
    public static final int TEXT_DURATION1 = 3;

    /**
     * Constant int for size of the regular text
     */
    public static final int TEXT_SIZE = 14;

    /**
     * Constant int for size of the larger  text
     */
    public static final int TEXT_SIZE_LARGE = 20;

    /**
     * Constant int for text duration of helper text (not in seconds)
     */
    public static final int HELP_TIME = 250;

    /**
     * Constant array listing all the phases
     */
    public static final List<String> PHASES
        = Arrays.asList("...", "story", "tell move", "tell attack", "tell use item", "exit room", "hotkeys");
    /**
     * Constant string for story text
     */
    public static final String STORY = """
        Welcome to mountain climber. You must battle your way
        to the top of the mountain. Inside various rooms
        you will encounter enemies and find treasures.""";
    /**
     * Constant string for move text
     */
    public static final String TELL_MOVE = "Move your character with the WASD keys.";
    /**
     * Constant string for attack text
     */
    public static final String TELL_ATTACK = "Use the battle button [f] to attack the enemies.";
    /**
     * Constant string for open chest text
     */
    public static final String TELL_USE_ITEM = "Open treasure chests using the [e] key..";
    /**
     * Constant string for good luck text
     */
    public static final String EXIT_ROOM = "Good luck on your journey!";
    /**
     * Constant string for telling player the game controls
     */
    public static final String CONTROLS = """
                   Hotkeys:  Press K for help. Press WASD to return to game.\s
        Game Controls:  Use WASD to move. Use battle key [f] to  \s
                         initiate battles. Open chests using [e].""";
    /**
     * Constant string for telling the player the hotkeys
     */
    public static final String HOTKEYS = "Hint: Press [WASD] to return to the game. Press K for help!";
    /**
     * Constant array containing the actual text shown to the player
     */
    public static final List<String> PHASES_TEXT = Arrays.asList("",
        TutorialTexts.STORY, TutorialTexts.TELL_MOVE, TutorialTexts.TELL_ATTACK, TutorialTexts.TELL_USE_ITEM, TutorialTexts.EXIT_ROOM, TutorialTexts.HOTKEYS);
    /**
     * Constant string reminding player how to move
     */
    public static final String DID_NOT_MOVE = """
        You haven't completed the tutorial yet ...

        You should return to the game and move you character with [WASD]!
        (Pressing WASD in this window will return you to the game.)
        """;

    /**
     * Constant string for reminding player to open chest
     */
    public static final String DID_NOT_OPEN_CHEST = """
        You haven't completed the tutorial yet ...

        You should go open a chest! Do this using the [e] key.
        (Pressing WASD in this window will return you to the game.)
        """;

    /**
     * Constant string for reminding player to initiate a battle
     */
    public static final String DID_NOT_BATTLE = """
        You haven't completed the tutorial yet ...

        You should go fight a battle! Use [f] key to initiate a battle.
        (Pressing WASD in this window will return you to the game.)
        """;
}

Added src/main/java/com/mg105/utils/UIConstants.java.






































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
package com.mg105.utils;

import javafx.scene.text.Font;

/**
 * General user interface constants.
 */
public class UIConstants {
    /**
     * The width and height of the entire canvas.
     */
    public static final int CANVAS_SIZE = MapConstants.TILE_SIZE * MapConstants.ROOM_SIZE;
    /**
     * The width of a line gap.
     */
    public static final int SINGLE_GAP = CANVAS_SIZE / 32;
    /**
     * The standard font.
     */
    public static final Font FONT_NORMAL = Font.loadFont(UIConstants.class.getResourceAsStream("/fonts/Strait-Regular.ttf"), 19);
    /**
     * The title font.
     */
    public static final Font TITLE_FONT = Font.loadFont(UIConstants.class.getResourceAsStream("/fonts/Strait-Regular.ttf"), 30);
    /**
     * The title font used only for the number.
     */
    public static final Font TITLE_FONT_ALT = Font.loadFont(UIConstants.class.getResourceAsStream("/fonts/HomemadeApple-Regular.ttf"), 35);
    /**
     * The subtitle font.
     */
    public static final Font SUBTITLE_FONT = Font.loadFont(UIConstants.class.getResourceAsStream("/fonts/Strait-Regular.ttf"), 22);
    /**
     * The keyboard font.
     */
    public static final Font FONT_MONO = Font.loadFont(UIConstants.class.getResourceAsStream("/fonts/IBMPlexMono-Medium.ttf"), 19);
}

Added src/main/resources/fonts/HomemadeApple-Regular.ttf.

cannot compute difference between binary files

Added src/main/resources/fonts/IBMPlexMono-Medium.ttf.

cannot compute difference between binary files

Added src/main/resources/fonts/Strait-Regular.ttf.

cannot compute difference between binary files

Changes to src/main/resources/tiles/battle.png.

cannot compute difference between binary files

Changes to src/main/resources/tiles/chest.png.

cannot compute difference between binary files

Changes to src/main/resources/tiles/chest_open.png.

cannot compute difference between binary files

Deleted src/main/resources/tiles/exit.png.

cannot compute difference between binary files

Changes to src/main/resources/tiles/wall_face.png.

cannot compute difference between binary files

Deleted src/test/java/com/mg105/entities/GiveTutorialTest.java.

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































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.entities;

import com.mg105.utils.TutorialTexts;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class GiveTutorialTest {
    @Test
    void testActionSetterGetter() {
        GiveTutorial newTutorial = new GiveTutorial(false, false, false);
        newTutorial.actionPerformedSetter(TutorialTexts.MOVED);
        GiveTutorial newTutorial2 = new GiveTutorial(false, false, false);
        newTutorial2.actionPerformedSetter(TutorialTexts.ATTACKED);
        GiveTutorial newTutorial3 = new GiveTutorial(false, false, false);
        newTutorial3.actionPerformedSetter(TutorialTexts.USED_ITEM);

        assertTrue(newTutorial.actionPerformedGetter(TutorialTexts.MOVED));
        assertFalse(newTutorial.actionPerformedGetter(TutorialTexts.ATTACKED));
        assertFalse(newTutorial.actionPerformedGetter(TutorialTexts.USED_ITEM));

        assertFalse(newTutorial2.actionPerformedGetter(TutorialTexts.MOVED));
        assertTrue(newTutorial2.actionPerformedGetter(TutorialTexts.ATTACKED));
        assertFalse(newTutorial2.actionPerformedGetter(TutorialTexts.USED_ITEM));

        assertFalse(newTutorial3.actionPerformedGetter(TutorialTexts.MOVED));
        assertFalse(newTutorial3.actionPerformedGetter(TutorialTexts.ATTACKED));
        assertTrue(newTutorial3.actionPerformedGetter(TutorialTexts.USED_ITEM));
    }
}

Deleted src/test/java/com/mg105/interface_adapters/map/RoomInterpreterTest.java.

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

















































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.interface_adapters.map;

import com.mg105.use_cases.map.RoomGetterInterface;
import com.mg105.use_cases.outputds.RoomLayout;
import com.mg105.utils.MapConstants;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class RoomInterpreterTest {
    @Test
    void getLayout() {
        RoomTileType[][] expected = new RoomTileType[][]{
            {RoomTileType.WALL, RoomTileType.EXIT, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL},
            {RoomTileType.WALL_WITH_FACE, RoomTileType.OPPONENT_SET, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.WALL},
            {RoomTileType.EXIT, RoomTileType.CHEST_OPEN, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.WALL_WITH_FACE},
            {RoomTileType.WALL, RoomTileType.CHEST, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.EXIT},
            {RoomTileType.WALL, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.WALL},
            {RoomTileType.WALL, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.WALL},
            {RoomTileType.WALL, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.FLOOR, RoomTileType.WALL},
            {RoomTileType.WALL_WITH_FACE, RoomTileType.EXIT, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE, RoomTileType.WALL_WITH_FACE},
        };
        RoomInterpreter interpreter = new RoomInterpreter(new FakeRoomGetter());
        RoomTileType[][] actual = interpreter.getCurrentRoom();

        for (int y = 0; y < MapConstants.ROOM_SIZE; y++) {
            for (int x = 0; x < MapConstants.ROOM_SIZE; x++) {
                assertEquals(expected[y][x], actual[y][x]);
            }
        }
    }

    @Test
    void getPlayer() {
        RoomInterpreter interpreter = new RoomInterpreter(new FakeRoomGetter());
        assertEquals(new Point(4, 4), interpreter.getPlayer());
    }

    @Test
    void getSprite() {
        RoomInterpreter interpreter = new RoomInterpreter(new FakeRoomGetter());
        assertEquals("/sprites/B.png", interpreter.getCharacterSprite());
    }

    private static class FakeRoomGetter implements RoomGetterInterface {
        @Override
        public @NotNull RoomLayout getCurrentRoomLayout() {
            List<Point> closedChests = new ArrayList<>();
            closedChests.add(new Point(1, 3));

            List<Point> openChests = new ArrayList<>();
            openChests.add(new Point(1, 2));

            List<Point> opponents = new ArrayList<>();
            opponents.add(new Point(1, 1));

            List<Point> doorways = new ArrayList<>();
            doorways.add(new Point(0, 2));
            doorways.add(new Point(1, 0));
            doorways.add(new Point(MapConstants.ROOM_SIZE - 1, 3));
            doorways.add(new Point(1, MapConstants.ROOM_SIZE - 1));

            return new RoomLayout(closedChests, openChests, opponents, doorways, new Point(4, 4));
        }

        @Override
        public @NotNull String getWalkingSprite() {
            return "/sprites/B.png";
        }

        @Override
        public boolean isFinalRoom() {
            return false;
        }
    }
}

Deleted src/test/java/com/mg105/use_cases/tutorial/TutorialInteractorTest.java.

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


























































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
package com.mg105.use_cases.tutorial;

import com.mg105.use_cases.PlayerGetsTutorial;
import com.mg105.user_interface.TutorialTextDisplay;
import com.mg105.utils.TutorialTexts;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class TutorialInteractorTest {
    @Test
    void testAdvancePhase() {
        PlayerGetsTutorial tutorialPlayer
            = new PlayerGetsTutorial(TutorialTexts.PHASES, 0);

        assertEquals(tutorialPlayer.currentPhase(), 0);
        tutorialPlayer.nextPhase();
        assertEquals(tutorialPlayer.currentPhase(), 1);
        tutorialPlayer.nextPhase();
        assertEquals(tutorialPlayer.currentPhase(), 2);
        tutorialPlayer.nextPhase();
        assertEquals(tutorialPlayer.currentPhase(), 3);
    }

    @Test
    void testTutorialComplete() {
        PlayerGetsTutorial tutorialPlayer1
            = new PlayerGetsTutorial(TutorialTexts.PHASES, 0);
        PlayerGetsTutorial tutorialPlayer2
            = new PlayerGetsTutorial(TutorialTexts.PHASES, 0);
        tutorialPlayer2.setActionPerformed(TutorialTexts.MOVED);
        tutorialPlayer2.setActionPerformed(TutorialTexts.ATTACKED);
        tutorialPlayer2.setActionPerformed(TutorialTexts.USED_ITEM);

        assertFalse(tutorialPlayer1.getActionPerformed(TutorialTexts.MOVED));
        assertFalse(tutorialPlayer1.getActionPerformed(TutorialTexts.ATTACKED));
        assertFalse(tutorialPlayer1.getActionPerformed(TutorialTexts.USED_ITEM));
        assertFalse(tutorialPlayer1.isComplete());

        assertTrue(tutorialPlayer2.getActionPerformed(TutorialTexts.MOVED));
        assertTrue(tutorialPlayer2.getActionPerformed(TutorialTexts.ATTACKED));
        assertTrue(tutorialPlayer2.getActionPerformed(TutorialTexts.USED_ITEM));
        assertTrue(tutorialPlayer2.isComplete());
    }

    @Test
    void testTutorialBottomText() {
        TutorialTextDisplay tutorialDisplay = new TutorialTextDisplay();
        int phase_num = tutorialDisplay.getController().getTutorial().currentPhase();
        String phase = tutorialDisplay.getController().getTutorial().allPhases().get(phase_num);
        String tutorialText = tutorialDisplay.showBottomText(phase);
        String expected = "";

        TutorialTextDisplay tutorialDisplay2 = new TutorialTextDisplay();
        tutorialDisplay2.getController().nextPhase();
        int phase_num2 = tutorialDisplay2.getController().getTutorial().currentPhase();
        String phase2 = tutorialDisplay2.getController().getTutorial().allPhases().get(phase_num2);
        String tutorialText2 = tutorialDisplay2.showBottomText(phase2);

        TutorialTextDisplay tutorialDisplay3 = new TutorialTextDisplay();
        tutorialDisplay3.getController().nextPhase();
        tutorialDisplay3.getController().nextPhase();
        int phase_num3 = tutorialDisplay3.getController().getTutorial().currentPhase();
        String phase3 = tutorialDisplay3.getController().getTutorial().allPhases().get(phase_num3);
        String tutorialText3 = tutorialDisplay3.showBottomText(phase3);

        TutorialTextDisplay tutorialDisplay4 = new TutorialTextDisplay();
        tutorialDisplay4.getController().nextPhase();
        tutorialDisplay4.getController().nextPhase();
        tutorialDisplay4.getController().nextPhase();
        int phase_num4 = tutorialDisplay4.getController().getTutorial().currentPhase();
        String phase4 = tutorialDisplay4.getController().getTutorial().allPhases().get(phase_num4);
        String tutorialText4 = tutorialDisplay4.showBottomText(phase4);

        TutorialTextDisplay tutorialDisplay5 = new TutorialTextDisplay();
        tutorialDisplay5.getController().nextPhase();
        tutorialDisplay5.getController().nextPhase();
        tutorialDisplay5.getController().nextPhase();
        tutorialDisplay5.getController().nextPhase();
        int phase_num5 = tutorialDisplay5.getController().getTutorial().currentPhase();
        String phase5 = tutorialDisplay5.getController().getTutorial().allPhases().get(phase_num5);
        String tutorialText5 = tutorialDisplay5.showBottomText(phase5);

        assertEquals(tutorialText, expected);
        assertEquals(tutorialText2, TutorialTexts.STORY);
        assertEquals(tutorialText3, TutorialTexts.TELL_MOVE);
        assertEquals(tutorialText4, TutorialTexts.TELL_ATTACK);
        assertEquals(tutorialText5, TutorialTexts.TELL_USE_ITEM);
    }
}