-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
362 lines (350 loc) · 26.7 KB
/
index.html
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
<!DOCTYPE html>
<html>
<head>
<title>STM32 Maze game</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css.css">
<link rel="stylesheet" href="/emgithub.css">
<script type="text/javascript" src="/emgithub.js"></script>
</head>
<body>
<header>
<div class="wrapper">
<a href="/">Klemen's Stuff</a>
</div>
</header>
<div class="context sticky">
<div class="wrapper">
<div>
<a href="#"><svg height="20px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-10 -250 1674 1792">
<path fill="currentColor" d="M1408 992v480c0 35 -29 64 -64 64h-384v-384h-256v384h-384c-35 0 -64 -29 -64 -64v-480c0 -2 1 -4 1 -6l575 -474l575 474c1 2 1 4 1 6zM1631 923l-62 74c-5 6 -13 10 -21 11h-3c-8 0 -15 -2 -21 -7l-692 -577l-692 577c-7 5 -15 8 -24 7c-8 -1 -16 -5 -21 -11l-62 -74 c-11 -13 -9 -34 4 -45l719 -599c42 -35 110 -35 152 0l244 204v-195c0 -18 14 -32 32 -32h192c18 0 32 14 32 32v408l219 182c13 11 15 32 4 45z"/>
</svg></a> |
<a href="#misko">Miško 3</a> |
<span class="dropdown">
<a href="#maze">Maze</a> |
<div class="submenu">
<a href="#maze-gen">Maze generation</a> |
<a href="#maze-vis">Maze visualising</a>
</div>
</span>
<span class="dropdown">
<a href="#game">Interactions</a> |
<div class="submenu">
<a href="#game-menu">Main menu</a> |
<a href="#game-mazeplayer">Maze game</a>
</div>
</span>
<a href="#case">Case</a> |
<a href="https://github.com/Klemen2/VIN-Projekt/tree/main/VIN_Misko3_Project">Source code</a>
</div>
</div>
</div>
<div class="content">
<div class="wrapper">
<div class="title">Making a maze game on STM32G4</div>
The goal of the project is to implement a maze game using C programing language. I'll be using a
<a href="https://github.com/LAPSyLAB/Misko3_Docs_and_Projects">demo project<sup class="print">1</sup></a> that allready has
implemented some basic functionalities and additional drivers.
<span class="print"><br><sup>1 </sup>https://github.com/LAPSyLAB/Misko3_Docs_and_Projects</span>
<div class="center">
<figure>
<video width="400" controls>
<source src="images/gameplay.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<figcaption><span class="print">Video of a</span><span class="noprint">A</span> maze game gameplay on Miško 3</figcaption>
</figure>
</div>
<h1 id="parts">Parts list</h1>
<ul>
<li><a href="http://lpvo.fe.uni-lj.si/izobrazevanje/1-stopnja-vs/osnove-mikroprocesorske-elektronike-ome/">Miško 3</a> an STM32G474QETx board
</li>
</ul>
<h1 id="misko">Miško 3</h1>
Miško 3 is a development board for learning programming on microcontroller. The name stands for
Microcontroller Student Kit (<b>MI</b>krokrmilniški <b>Š</b>tudentski <b>KO</b>mplet) 3rd generation and it's powerd by
STM32G474QETx, an ARM-based 32-bit MCU. It has 12 PWM pins, 4 serial communications (RX, TX), 30 digital pins, 12 analog pins and can supply 3.3V, 5V and 12V.
<br>You can learn more about the MCU <a class="noprint" href="https://www.st.com/resource/en/reference_manual/rm0440-stm32g4-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf">here</a><span class="print">on https://www.st.com/resource/en/reference_manual/rm0440-stm32g4-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf </span>.
<p>Before we can use the board, we need to solder on 7 buttons, 8 leds, connector headers and the screen (XPT2046)
as well as connect the joystick and srew it in place.
<div class="center">
<figure style="width: 372px">
<picture>
<source srcset="images/soldering.jxl" type="image/jxl">
<img src="images/soldering.png" loading="lazy">
</picture>
<figcaption>Miško 3 - soldering reset button</figcaption>
</figure>
</div>
The final product should looks like this:
<div class="imageRow pagebreak">
<figure>
<picture>
<source srcset="images/front.jxl" type="image/jxl">
<img src="images/front.png" loading="lazy">
</picture>
<figcaption>Miško 3 - front side</figcaption>
</figure>
<figure>
<picture>
<source srcset="images/back.jxl" type="image/jxl">
<img src="images/back.png" loading="lazy">
</picture>
<figcaption>Miško 3 - back side</figcaption>
</figure>
</div>
<h1 id="maze">Maze</h1>
There are many different <a href="https://en.wikipedia.org/wiki/Maze">types of mazes<sup class="print">1</sup></a>: standard, circular, block, number... For this project we'll implement a standard
maze without entrance and exit where the goal is to find the shortest path from one position to another. For storing the maze we'll use an array where 1 will be a wall and 0 will be a path.
<span class="print"><br><sup>1 </sup>https://en.wikipedia.org/wiki/Maze</span>
<div class="imageRow">
<figure>
<picture>
<source srcset="images/Maze_Type_Standard.jxl" type="image/jxl">
<img src="images/Maze_Type_Standard.png" loading="lazy">
</picture>
<figcaption>Standard maze with entrance and exit</figcaption>
</figure>
<figure>
<picture>
<source srcset="images/Maze_Type_Circular.jxl" type="image/jxl">
<img src="images/Maze_Type_Circular.png" loading="lazy">
</picture>
<figcaption>Circular maze</figcaption>
</figure>
<figure>
<picture>
<source srcset="images/Maze_Type_Block.jxl" type="image/jxl">
<img src="images/Maze_Type_Block.png" loading="lazy">
</picture>
<figcaption>Block maze</figcaption>
</figure>
</div>
<h2 id="maze-gen">Generating</h2>
There are many <a href="https://en.wikipedia.org/wiki/Maze_generation_algorithm">different aproches for randomly generating mazes<sup class="print">1</sup></a>
such as randomized depth-first search, Kruskal's algorithm, Prim's algorithm... The algorithm that we are going to implement is randomized depth-first search with iterative implementation.
Before we implement everything from scratch we should search the web if there is allredy something similar allready made. I found just what we need on <a href="https://github.com/Figglewatts/mazegen">Figglewatts's github<sup class="print">2</sup></a>
which we'll modifiy to suit our needs.
<p>Since iterative implementation of random dept-first search is implemented using a stack, we have to implement it first before we even think of
making algorithm. Stack is an abstract data type which serves as a collection of elements that has two main operations: push (adds element) and pop (removes last added element).
These two operations are implemented as stack_push and stack_pop. Since there is no built-in implementation of array that automatically resizes in C, we use a pointer to allocated space,
which we realocate to double size when it runs out of allocated capacity.
<span class="print"><br><sup>1 </sup>https://en.wikipedia.org/wiki/Maze_generation_algorithm</span>
<span class="print"><br><sup>2 </sup>https://github.com/Figglewatts/mazegen</span>
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Inc/stack.h#L8-L47")</script>
After implementation of stack, we can implement a interative random dept-first search. The maze that we're implementing has 2 different axis, x and y axis,
therefore we need 2 stacks to store each axis without over complicating functionality of the stack. For implementation of the algorithm we run a while loop
until one of the stack is empty. Which stack we check is not important since they are always the same size. To make the algorithm random we first shuffle the directions of posible moves in current position.
If the planned direction is at the edge or it is allready a path (zero), nothing happens, otherwise a path is made (zero is written in two next cells on planned directions)
and the position with that direction is pushed to a stack.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/mazeGenerator.c#L10-L92")</script>
Since the above algorithm is just for carving the maze, we also need a function to initialize all variables and allocate enough space for a maze of defined
size and fill it with walls (ones). When the algorithm is done carving the maze, the generated maze is returned to the main program where we implement the logic for
player movement and visualise it.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/mazeGenerator.c#L95-L117")</script>
<div class="pagebreak"></div>
<h2 id="maze-vis">Visualising</h2>
For visualising the maze, we will use <a href="http://embeddedlightning.com/download/reference-guide/">μGUI</a><sup class="print">1</sup>, a free and open source library for embedded systems. The algorithm used for drawing the walls will draw white lines of
defined size with <code>UG_DrawLine</code>. To optimize the algorithm, the bottom and right edge are drawn outside the double for loops. At the end we draw a target position, a green
rectangle with <code>UG_DrawFrame</code> which only draws a border, on top right corner.
<span class="print"><br><sup>1 </sup>http://embeddedlightning.com/download/reference-guide/</span>
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/mazeGenerator.c#L119-L141")</script>
<h1 id="game">Interactions</h1>
The program contains of two main interactive scenes, main menu and maze game, with a posibility to quickly add new types of game
and controlls for it.
<h2 id="game-menu">Main menu</h2>
Main menu is the first thing that you can see when you power-up the Miško. The main part of the main menu are top text, that says "MENU",
and blue rectangles with white arrow on each side of the screen.
<div class="center pagebreak">
<figure style="width: 372px">
<picture>
<source srcset="images/mainmenu.jxl" type="image/jxl">
<img src="images/mainmenu.png" loading="lazy">
</picture>
<figcaption>Main menu</figcaption>
</figure>
</div>
To draw it we first use <code>UG_FillScreen</code> to make the whole screen black. Then we set a foreground and background color to draw the text
with <code>UG_PutString</code> and use <code>UG_FillFrame</code> to draw blue rectangles on each side.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/main.c#L177-L190")</script>
Since there are multiple settings for maze game I implemented "pages" witch contains a rounded square border with some
graphics, title and sequential page number on top left. If you want to add a new page, you simply change the amount of pages in predefined variable "pages"
and add a new case in the switch statement. Bellow function is used for refreshing the contetn of the page, which means changing
sequential number, title, graphics and border. If the page is not defined a default page with a blue border and title "SOON" will be displayed.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/main.c#L129-L174")</script>
We also need to implement menu controlls to switch between the pages. To increment the page we will use right button or move the joystick to the right and
to decrement the page we will use left button or move the joystick to the left. Page selection we will implement by ok button or joystick press, which will
also call a function to draw all the static content of the game page. <code>joystickSensitivity</code> is used for setting how far you need to move joystick
before action is made.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/main.c#L286-L299")</script>
We can also implemented touch controls, where pressing the left blue rectangle will decrement the page, right blue rectengle will increment the page and anywhere else will select the page.
The touch controlls are not the most accurate on this miško and pressing the touch screen will most likely select the page even if we accurately press the blue rectangles.
This is most likely caused by either slightly damaged screen or poor soldering of the screen connector.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/main.c#L245-L255")</script>
To draw all the static (not changing) content of the games, we can implement a function that will only be called when the page is selected.
Since we allready defined all the maze variables on pages, we can just group the three cases and use the same functionality for drawing the maze.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/main.c#L219-L241")</script>
<h2 id="game-mazeplayer">Maze game</h2>
Maze game contains a maze, move counter and a yellow circle which represent current player position.
<p>We will implemented three different presets of the maze: easy, normal and hard; which is defined in <code>MainMenuRefresh()</code> shown above.
Easy dificulty will of size 21x13 and have a cell size of 31, with a (5,43) offset and a player circle radius 7.
Normal dificulty will of size 29x19 and have a cell size of 21, with a (12,40) offset and a player circle radius 5.
Hard dificulty will of size 51x33 and have a cell size of 12, with a (10,40) offset and a player circle radius 2.
<div class="imageRow">
<figure>
<picture>
<source srcset="images/maze_easy.jxl" type="image/jxl">
<img src="images/maze_easy.png" loading="lazy">
</picture>
<figcaption>Easy dificulty</figcaption>
</figure>
<figure>
<picture>
<source srcset="images/maze_normal.jxl" type="image/jxl">
<img src="images/maze_normal.png" loading="lazy">
</picture>
<figcaption>Normal dificulty</figcaption>
</figure>
<figure>
<picture>
<source srcset="images/maze_hard.jxl" type="image/jxl">
<img src="images/maze_hard.png" loading="lazy">
</picture>
<figcaption>Hard dificulty</figcaption>
</figure>
</div>
For controlls we will use up, down, left and right button or a joystick to move in given direction.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/main.c#L273-L283")</script>
Since in a maze you cannot freely move in all directions we also need to implement a logic which will check if in a given
direction is a wall and move the player if there isn't one. When moving a player, we first need to clear the drawn circle in previous
position and then draw it on a new position.
When player gets to the green square, a new maze get's generated and player position is set back to left bottom square.
<script>embed("https://github.com/Klemen2/VIN-Projekt/blob/949416fb613e5803996c3436a7225f9c649cb38d/VIN_Misko3_Project/Core/Src/main.c#L196-L217")</script>
When testing the game I noticed that my joystick has a constant offset of (-6,6) so I added 2 more variables
to joystick.c and joystick.h which fixes this issue and gives more accurate joystick controlls.
<h1 id="case">Case</h1>
Since standalone Miško is not the most comfortable thing to hold in hands I aslo designed a cases for easier useage of it.
The cases were designed in blender and you can find them <a class="noprint" href="https://github.com/Klemen2/VIN-Projekt/tree/main/case">here</a><span class="print">https://github.com/Klemen2/VIN-Projekt/tree/main/case</span>.
<h2>Version 1</h2>
Version 1 of the case has a few flaws. Before printing the case needs to be scaled to match 3D printer
tolerances, since it's made for a super tight fit and will need a lot of sanding if you decide not to do so.
There are some other areas that needs sanding eaither way such as joystick hole, since it's too small and
shifted,
holes for the pins on the back, due to one of them being shifted, and the buttons guides, they are way too
big.
The front buttons are also too small for comfortable use, but it's much better than to use it without a
case.
<p>If the case doesn't stay toggether you'll also need to widen 3 holes on top and bottom side (the hole
next to the reset button does not work well) and
use appropiet screws, i'm using 10mm long screws Ø2 that I got from an old Lenovo laptop.
<div class="imageRow">
<figure>
<picture>
<source srcset="images/v1_front.jxl" type="image/jxl">
<img src="images/v1_front.png" loading="lazy">
</picture>
<figcaption>Case version 1 - front side</figcaption>
</figure>
<figure>
<picture>
<source srcset="images/v1_back.jxl" type="image/jxl">
<img src="images/v1_back.png" loading="lazy">
</picture>
<figcaption>Case version 1 - back side</figcaption>
</figure>
</div>
<h2>Version 2</h2>
In version 2 of the case I fixed issues that I had with the version 1 of the case, such as scaling, bigger hole for
pins, bigger front buttons (the back reset button is still a small so you don't press it by accident).
I also removed the screw hole next to the reset button since there just isn't enough space for it.
<p>The only problem that I had was the same as before where the hole for joystick is too small, even though I made it bigger.
I allready made it bigger in uploaded files, but haven't tested if it's big enough.
<br>
<div class="imageRow">
<figure>
<picture>
<source srcset="images/v2_front.jxl" type="image/jxl">
<img src="images/v2_front.png" loading="lazy">
</picture>
<figcaption>Case version 2 - front side</figcaption>
</figure>
<figure>
<picture>
<source srcset="images/v2_back.jxl" type="image/jxl">
<img src="images/v2_back.png" loading="lazy">
</picture>
<figcaption>Case version 2 - back side</figcaption>
</figure>
</div>
</div>
</div>
<footer>
<div class="wrapper center links">
<div class="wrapper center links">
<a href="https://github.com/Klemen2" class="center">
<svg height="20" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="16"
data-view-component="true" class="octicon octicon-mark-github v-align-middle">
<path fill-rule="evenodd"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z">
</path>
</svg>
<span class="padding-left noprint">Klemen2</span>
<span class="padding-left print">https://github.com/Klemen2</span>
</a>
<a href="mailto:[email protected]" class="center">
<svg height="20" color="black" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-10 0 1812 1792">
<path fill="currentColor" d="M1792 710v794c0 88 -72 160 -160 160h-1472c-88 0 -160 -72 -160 -160v-794c30 33 64 62 101 87c166 113 334 226 497 345c84 62 188 138 297 138h1h1c109 0 213 -76 297 -138c163 -118 331 -232 498 -345c36 -25 70 -54 100 -87zM1792 416c0 112 -83 213 -171 274 c-156 108 -313 216 -468 325c-65 45 -175 137 -256 137h-1h-1c-81 0 -191 -92 -256 -137c-155 -109 -312 -217 -467 -325c-71 -48 -172 -161 -172 -252c0 -98 53 -182 160 -182h1472c87 0 160 72 160 160z"/>
</svg>
<span class="padding-left noprint">Send me an email</span>
<span class="padding-left print">[email protected]</span>
</a>
<a href="https://ko-fi.com/klemen2" class="center">
<svg height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
id="Layer_1" data-name="Layer 1" viewBox="0 0 504.36 504.36">
<defs>
<style>
.cls-1 {
fill: #00b9fe;
}
.cls-2 {
fill: #fff;
stroke: #000;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 1.14px;
}
.cls-3 {
fill: #ff5e5b;
}
.cls-4 {
fill: url(#linear-gradient);
}
</style>
<linearGradient id="linear-gradient" x1="163.6" y1="2319.39" x2="216.72" y2="2482.47"
gradientTransform="translate(4.26 -2219.68)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ff4ea3" />
<stop offset="1" stop-color="#ff5e5b" />
</linearGradient>
</defs>
<title>ko-fi</title>
<circle class="cls-1" cx="252.18" cy="252.18" r="252.18" />
<g id="Layer_1-2" data-name="Layer 1-2">
<g id="Layer_1-3" data-name="Layer 1-3">
<path class="cls-2"
d="M380.19,276.5A196.26,196.26,0,0,1,352,277.78V185.62h19.2a38.37,38.37,0,0,1,32,15.36,45.65,45.65,0,0,1,10.24,29.44A42.87,42.87,0,0,1,380.19,276.5Zm79.37-64a83.86,83.86,0,0,0-37.13-57.61A98.23,98.23,0,0,0,366.11,137H84.49a16.37,16.37,0,0,0-14.08,15.36v3.84s-1.28,124.17,1.28,192a42.11,42.11,0,0,0,42.24,39.68s129.29,0,190.73-1.28h9c35.84-9,38.4-42.24,38.4-60.16C422.43,329,472.36,279.06,459.56,212.5Z" />
<path class="cls-3"
d="M208.66,334.11c3.84,1.28,5.12,0,5.12,0s44.8-41,65.28-65.29c17.92-21.76,19.2-56.32-11.52-70.4s-56.32,15.36-56.32,15.36a50.44,50.44,0,0,0-70.41-7.68l-1.28,1.28c-15.36,16.64-10.24,44.8,1.28,60.16a771.87,771.87,0,0,0,65.29,64Z" />
<path class="cls-4"
d="M211.22,335.39a4.75,4.75,0,0,0,3.84-1.28s44.8-41,65.28-65.29c17.92-21.76,19.2-56.32-11.52-70.4s-56.32,15.36-56.32,15.36a50.44,50.44,0,0,0-70.41-7.68l-1.28,1.28c-15.36,16.64-10.24,44.8,1.28,60.16a799.58,799.58,0,0,0,66.57,65.29C208.66,335.39,209.94,335.39,211.22,335.39Z" />
</g>
</g>
</svg>
<span class="padding-left noprint">Buy me a Ko-fi</span>
<span class="padding-left print">https://ko-fi.com/klemen2</span>
</a>
</div>
</div>
</footer>
</body>
</html>