Compare commits

...

No commits in common. "v1.2.0" and "main" have entirely different histories.
v1.2.0 ... main

31 changed files with 609 additions and 60 deletions

View File

@ -19,8 +19,13 @@ jobs:
with:
go-version: "1.21"
- name: Set up fyne dependencies
run: sudo apt install libxcursor-dev libxinerama-dev libxrandr-dev libxi-dev libgl-dev libxxf86vm-dev
- name: Build
run: go build -v ./...
run: |
go build -v ./...
go build maze-solver
- name: Test
run: go test -v ./...
@ -31,3 +36,4 @@ jobs:
automatic_release_tag: "latest"
prerelease: true
title: "Development Build"
files: maze-solver

View File

@ -19,8 +19,13 @@ jobs:
with:
go-version: "1.21"
- name: Set up fyne dependencies
run: sudo apt install libxcursor-dev libxinerama-dev libxrandr-dev libxi-dev libgl-dev libxxf86vm-dev
- name: Build
run: go build -v ./...
run: |
go build -v ./...
go build maze-solver
- name: Test
run: go test -v ./...
@ -29,3 +34,4 @@ jobs:
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
files: maze-solver

2
.gitignore vendored
View File

@ -21,4 +21,4 @@
go.work
/Session.vim
/maze.png
/maze_sol.png
/sol.png

89
README.md Normal file
View File

@ -0,0 +1,89 @@
# maze-solver-go
## Introduction
This is a simple little maze solver made for fun and practise writing code in
golang. This project is a complete re-write of another maze solver I've written
in Java back in 2018 after the first semester of uni at EPFL. Needless to say,
this version is _way_ better as I have written it now that I have my Bachelor's
degree in Computer Science.
### Goal of the project
The goal of this side project was to deepen my understanding of path-finding
algorithms, together with trying to create a good design, focusing on
dependency injection and unit testing. It was very instructive.
Not to brag or anything, but the design was quite good, because after
completing the following steps of the project:
- reading a maze;
- solving it;
- writing the solution to the file-system;
an idea came to my mind: add the feature of visualizing the progress of the
solving algorithm by opening a window that displays the progress of the solver.
Well, each module (readers, writers and solvers) were design to be as decoupled
as possible, and it allowed me to implement the `visualizer` feature in only a
couple of hours (which I was honestly not expecting).
## Dependencies
- `go` >= 1.21
- `ffmpeg` (optional, for video visualization, see [visualization methods](#visulazation-methods))
## Usage
After downloading `maze-solver` from the
[assets of the latest release](releases/latest "Latest release"), you can use
it with the following arguments
| Short | Long | Default | Description |
| ----- | ----------------- | ---------- | ----------------------------------------------------------------------------------------- |
| -h | --help | | Print help information |
| -v | --verbose | 0 | Verbose level of the solver, see [verbose levels](#verbose-levels) |
| -i | --input | `maze.png` | Input file, can be a `.txt` or `.png` file |
| -o | --output | `sol.png` | Output file, can be a `.txt` or `.png` file |
| | --path-char-in | `' '` | Character to represent the path in an input text file. |
| | --wall-char-in | `'#'` | Character to represent the wall in an input text file. |
| | --path-char-out | `' '` | Character to represent the path in an output text file. |
| | --wall-char-out | `'#'` | Character to represent the wall in an output text file. |
| | --cell-size-in | 3 | Size of a cell (in pixels) for input file of image type. |
| | --cell-size-out | 3 | Size of a cell (in pixels) for output file of image type. |
| -a | --algo | a-star | Algorithm to solve the maze, see [solving algorithms](#solving-algorithms) |
| | --visualize | | Visualizer the progress of the solver, see [visualization methods](#visulazation-methods) |
| | --video-name | `sol.mp4` | Name of the output file if --visualize is set to 'video'. |
| | --video-framerate | 60 | Framerate of the video if --visualize is set to 'video'. |
### Verbose levels
| Level | Description |
| ----- | ------------------------------------------------------------------------- |
| 0 | nothing printed to stdout |
| 1 | print the total time taken by the solver (time of the main() function) |
| 2 | prints the time the solving algorithm took to run |
| 3 | prints the time taken by each section (reader, solving algorithm, writer) |
### Visualization methods
| Option | Description |
| ------ | ---------------------------------------------------------------------- |
| window | will give a live feed of the solver |
| video | creates a video where each frame is a step the solving algorithm takes |
### Solving algorithms
| Option | Description |
| -------- | -------------------------------------------------------------------------------------------------------------- |
| dfs | [Depth-first search](https://en.wikipedia.org/wiki/Depth-first_search "Wikipedia: Depth-first search") |
| bfs | [Breadth-first search](https://en.wikipedia.org/wiki/Breadth-first_search "Wikipedia: Breadth-first search") |
| dijkstra | [Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm "Wikipedia: Dijkstra's algorithm") |
| a-star | [A\*](https://en.wikipedia.org/wiki/A*_search_algorithm "Wikipedia: A* search algorithm") |
## Examples
| BFS | DFS |
| :----------------------------------------: | :-----------------------------------: |
| ![bfs](./assets/videos/bfs.gif) | ![dfs](./assets/videos/dfs.gif) |
| ![disjkstra](./assets/videos/dijkstra.gif) | ![a-star](./assets/videos/a-star.gif) |
| **Dijkstra** | **A\*** |

BIN
assets/mid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/solved/a-star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
assets/solved/bfs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
assets/solved/dfs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
assets/solved/dijkstra.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
assets/videos/a-star.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

BIN
assets/videos/a-star.mp4 Normal file

Binary file not shown.

BIN
assets/videos/bfs.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

BIN
assets/videos/bfs.mid.mp4 Normal file

Binary file not shown.

BIN
assets/videos/dfs.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

BIN
assets/videos/dfs.mid.mp4 Normal file

Binary file not shown.

BIN
assets/videos/dijkstra.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 MiB

Binary file not shown.

19
go.mod
View File

@ -3,11 +3,30 @@ module maze-solver
go 1.21
require (
fyne.io/fyne v1.4.3
github.com/akamensky/argparse v1.4.0
github.com/mazznoer/colorgrad v0.9.1
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fyne-io/mobile v0.1.2 // indirect
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/net v0.6.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.12.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mazznoer/csscolorparser v0.1.2 // indirect

81
go.sum
View File

@ -1,43 +1,124 @@
fyne.io/fyne v1.4.3 h1:356CnXCiYrrfaLGsB7qLK3c6ktzyh8WR05v/2RBu51I=
fyne.io/fyne v1.4.3/go.mod h1:8kiPBNSDmuplxs9WnKCkaWYqbcXFy0DeAzwa6PBO9Z8=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fyne-io/mobile v0.1.2 h1:0HaXDtOOwyOTn3Umi0uKVCOgJtfX73c6unC4U8i5VZU=
github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8=
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
github.com/mazznoer/colorgrad v0.9.1 h1:MB80JYVndKWSMEM1beNqnuOowWGhoQc3DXWXkFp6JlM=
github.com/mazznoer/colorgrad v0.9.1/go.mod h1:WX2R9wt9B47+txJZVVpM9LY+LAGIdi4lTI5wIyreDH4=
github.com/mazznoer/csscolorparser v0.1.2 h1:/UBHuQg792ePmGFzTQAC9u+XbFr7/HzP/Gj70Phyz2A=
github.com/mazznoer/csscolorparser v0.1.2/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA=
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY=
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -28,6 +28,18 @@ func (w *ImageWriter) Write() error {
return errors.New("Filename of ImageWriter doesn't have .png extension. The only suppported image type is png")
}
w.GenerateImage()
f, err := os.Create(w.Filename)
if err != nil {
return err
}
png.Encode(f, w.img)
f.Close()
return nil
}
func (w *ImageWriter) GenerateImage() *image.RGBA {
w.img = image.NewRGBA(image.Rect(0, 0, w.Maze.Width*w.CellWidth, w.Maze.Height*w.CellHeight))
// Fill the image with walls
@ -50,6 +62,9 @@ func (w *ImageWriter) Write() error {
w.draw(x0, y0, width, height, w.PathColor)
}
}
if len(w.Maze.Solution) == 0 {
return w.img
}
// Fill in the solution
total_len := w.getSolutionLength()
@ -99,14 +114,7 @@ func (w *ImageWriter) Write() error {
w.draw(x0, y0, width, height, colors[c])
}
}
f, err := os.Create(w.Filename)
if err != nil {
return err
}
png.Encode(f, w.img)
f.Close()
return nil
return w.img
}
func (w *ImageWriter) getSolutionLength() int {

77
main.go
View File

@ -6,18 +6,21 @@ import (
"image/color"
"maze-solver/io/reader"
"maze-solver/io/writer"
"maze-solver/maze"
"maze-solver/maze/parser"
"maze-solver/solver"
"maze-solver/utils"
"maze-solver/visualizer"
"os"
"strings"
"sync"
"github.com/akamensky/argparse"
"github.com/mazznoer/colorgrad"
)
func main() {
readerFactory, writerFactory, solverFactory, ok := parse_arguments()
readerFactory, writerFactory, solverFactory, visFactory, ok := parse_arguments()
if !ok {
return
@ -26,19 +29,49 @@ func main() {
defer utils.Timer("TOTAL", 1)()
reader := readerFactory.Get()
maze, err := parser.Parse(reader)
m, err := parser.Parse(reader)
utils.Check(err, "Couldn't read maze")
solver := solverFactory.Get()
solved := solver.Solve(maze)
var solved *maze.SolvedMaze
if *visFactory.Type != "" {
solved_chan := make(chan *maze.SolvedMaze, 3)
solver := solverFactory.Get(solved_chan)
vis := visFactory.Get()
vis.Init(m)
var wg sync.WaitGroup
wg.Add(2)
lets_go := make(chan bool, 1)
go func() {
if <-lets_go {
solved = solver.Solve(m)
}
close(solved_chan)
wg.Done()
}()
go func() {
vis.Visualize(solved_chan)
wg.Done()
}()
vis.Run(lets_go)
wg.Wait()
} else {
solver := solverFactory.Get(nil)
solved = solver.Solve(m)
}
if solved == nil { // cuz maybe with the window visualization, the user pressed "no"
return
}
writer := writerFactory.Get(solved)
err = writer.Write()
utils.Check(err, "Couldn't write solved maze")
}
func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.SolverFactory, bool) {
func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.SolverFactory, *visualizer.VisualizerFactory, bool) {
argparser := argparse.NewParser("maze-solver", "Solves the given maze (insane, right? who would've guessed?)")
var verboseLevel *int = argparser.FlagCounter("v", "verbose", &argparse.Options{
@ -52,6 +85,7 @@ func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.So
readerFactory := reader.ReaderFactory{}
writerFactory := writer.WriterFactory{}
solverFactory := solver.SolverFactory{}
visFactory := visualizer.VisualizerFactory{}
readerFactory.Type = reader.TYPES[".png"]
readerFactory.Filename = argparser.String("i", "input", &argparse.Options{
@ -71,8 +105,8 @@ func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.So
writerFactory.Type = writer.TYPES[".png"]
writerFactory.Filename = argparser.String("o", "output", &argparse.Options{
Help: "Input file",
Default: "maze_sol.png",
Help: "Output file",
Default: "sol.png",
Validate: func(args []string) error {
var ok bool
extension := args[0][len(args[0])-4:]
@ -86,7 +120,7 @@ func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.So
})
readerFactory.PathChar = argparser.String("", "path-char-in", &argparse.Options{
Help: "Character to represent the path in a input text file",
Help: "Character to represent the path in an input text file",
Default: " ",
Validate: func(args []string) error {
if len(args[0]) > 1 {
@ -97,7 +131,7 @@ func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.So
})
readerFactory.WallChar = argparser.String("", "wall-char-in", &argparse.Options{
Help: "Character to represent the wall in a input text file",
Help: "Character to represent the wall in an input text file",
Default: "#",
Validate: func(args []string) error {
if len(args[0]) > 1 {
@ -108,7 +142,7 @@ func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.So
})
writerFactory.PathChar = argparser.String("", "path-char-out", &argparse.Options{
Help: "Character to represent the path in a output text file",
Help: "Character to represent the path in an output text file",
Default: " ",
Validate: func(args []string) error {
if len(args[0]) > 1 {
@ -119,7 +153,7 @@ func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.So
})
writerFactory.WallChar = argparser.String("", "wall-char-out", &argparse.Options{
Help: "Character to represent the wall in a output text file",
Help: "Character to represent the wall in an output text file",
Default: "#",
Validate: func(args []string) error {
if len(args[0]) > 1 {
@ -140,13 +174,28 @@ func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.So
})
solverFactory.Type = argparser.Selector("a", "algo", solver.TYPES, &argparse.Options{
Help: fmt.Sprintf("Algorithm to solve the maze, avaiable options: %s", strings.Join(solver.TYPES, ", ")),
Help: fmt.Sprintf("Algorithm to solve the maze, available options: %s", strings.Join(solver.TYPES, ", ")),
Default: solver.TYPES[0],
})
visFactory.Type = argparser.Selector("", "visualize", visualizer.VIZ_METHODS, &argparse.Options{
Help: fmt.Sprintf("Visualizer the progress of the solver, available options: %s. Window will give a live feed of the solver, whereas video creates a video creates a video where each frame is a step the solving algorithm takes", strings.Join(visualizer.VIZ_METHODS, ", ")),
Default: "",
})
visFactory.Filename = argparser.String("", "video-name", &argparse.Options{
Help: "Name of the output file if --visualize is set to 'video'",
Default: "sol.mp4",
})
visFactory.Framerate = argparser.Float("", "video-framerate", &argparse.Options{
Help: "Framerate of the video if --visualize is set to 'video'",
Default: 60.,
})
if err := argparser.Parse(os.Args); err != nil {
fmt.Println(argparser.Usage(err))
return nil, nil, nil, false
return nil, nil, nil, nil, false
}
utils.VERBOSE_LEVEL = *verboseLevel
@ -159,5 +208,5 @@ func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.So
writerFactory.PathColor = color.RGBA{255, 255, 255, 255}
writerFactory.SolutionGradient = colorgrad.Warm()
return &readerFactory, &writerFactory, &solverFactory, true
return &readerFactory, &writerFactory, &solverFactory, &visFactory, true
}

75
solver/a-star.go Normal file
View File

@ -0,0 +1,75 @@
package solver
import (
"maze-solver/maze"
"maze-solver/utils"
"sort"
)
type AStarSolver struct {
solved_chan chan<- *maze.SolvedMaze
dist_from_start map[*maze.Node]int
dist_from_end map[*maze.Node]int
parent map[*maze.Node]*maze.Node
stack sorted_stack
}
func (s *AStarSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
defer utils.Timer("A* algorithm", 2)()
s.dist_from_start = make(map[*maze.Node]int, len(m.Nodes))
s.dist_from_end = make(map[*maze.Node]int, len(m.Nodes))
s.parent = make(map[*maze.Node]*maze.Node, len(m.Nodes))
current, end := m.Nodes[0], m.Nodes[len(m.Nodes)-1]
for _, node := range m.Nodes {
s.dist_from_start[node] = 0
s.dist_from_end[node] = int(node.Coords.Distance(end.Coords))
}
for current != end {
current.Visited = true
if s.solved_chan != nil {
s.solved_chan <- &maze.SolvedMaze{
Maze: m,
Solution: s.generateSolution(current, m),
}
}
for _, child := range []*maze.Node{current.Left, current.Right, current.Up, current.Down} {
if child != nil {
dist := s.dist_from_start[current] + int(current.Coords.Distance(child.Coords))
if !child.Visited {
s.parent[child] = current
s.dist_from_start[child] = dist
s.stack.insert(child, &s.dist_from_end)
} else if s.dist_from_start[child] > dist {
s.parent[child] = current
s.dist_from_start[child] = dist
sort.Slice(s.stack, func(i, j int) bool {
return s.dist_from_end[s.stack[i]] < s.dist_from_end[s.stack[j]]
})
}
}
}
current = s.stack.pop()
}
return &maze.SolvedMaze{
Maze: m,
Solution: s.generateSolution(current, m),
}
}
func (s *AStarSolver) generateSolution(current *maze.Node, m *maze.Maze) []*maze.Node {
solution := make([]*maze.Node, 0, len(m.Nodes))
for current != m.Nodes[0] {
solution = append(solution, current)
current = s.parent[current]
}
solution = append(solution, m.Nodes[0])
for i, j := 0, len(solution)-1; i < j; i, j = i+1, j-1 {
solution[i], solution[j] = solution[j], solution[i]
}
return solution
}

View File

@ -9,7 +9,8 @@ import (
)
type BFSSolver struct {
queue *Queue
solved_chan chan<- *maze.SolvedMaze
queue *Queue
}
type Queue struct {
@ -87,6 +88,12 @@ func (s *BFSSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
var err error
for current != end {
current.Visited = true
if s.solved_chan != nil {
s.solved_chan <- &maze.SolvedMaze{
Maze: m,
Solution: current_history,
}
}
s.addIfNotVisited(current.Down, current_history)
s.addIfNotVisited(current.Left, current_history)
@ -99,6 +106,12 @@ func (s *BFSSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
}
current = current_history[len(current_history)-1]
}
if s.solved_chan != nil {
s.solved_chan <- &maze.SolvedMaze{
Maze: m,
Solution: current_history,
}
}
return &maze.SolvedMaze{
Maze: m,

View File

@ -5,7 +5,9 @@ import (
"maze-solver/utils"
)
type DFSSolver struct{}
type DFSSolver struct {
solved_chan chan<- *maze.SolvedMaze
}
func (s *DFSSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
defer utils.Timer("DFS algorithm", 2)()
@ -17,6 +19,12 @@ func (s *DFSSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
for current != end {
current.Visited = true
if s.solved_chan != nil {
s.solved_chan <- &maze.SolvedMaze{
Maze: m,
Solution: stack,
}
}
left_visited, right_visited, up_visited, down_visited := visited(current.Left), visited(current.Right), visited(current.Up), visited(current.Down)

View File

@ -3,18 +3,16 @@ package solver
import (
"maze-solver/maze"
"maze-solver/utils"
"slices"
"sort"
)
type DijkstraSolver struct {
solved_chan chan<- *maze.SolvedMaze
dist_from_start map[*maze.Node]int
parent map[*maze.Node]*maze.Node
stack sorted_stack
}
type sorted_stack []*maze.Node
func (s *DijkstraSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
defer utils.Timer("Dijkstra algorithm", 2)()
s.dist_from_start = make(map[*maze.Node]int, len(m.Nodes))
@ -28,7 +26,6 @@ func (s *DijkstraSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
for current != end {
current.Visited = true
for _, child := range []*maze.Node{current.Left, current.Right, current.Up, current.Down} {
if child != nil {
dist := s.dist_from_start[current] + int(current.Coords.Distance(child.Coords))
@ -46,40 +43,30 @@ func (s *DijkstraSolver) Solve(m *maze.Maze) *maze.SolvedMaze {
}
}
current = s.stack.pop()
if s.solved_chan != nil {
s.solved_chan <- &maze.SolvedMaze{
Maze: m,
Solution: s.generateSolution(current, m),
}
}
}
return &maze.SolvedMaze{
Maze: m,
Solution: s.generateSolution(current, m),
}
}
func (s *DijkstraSolver) generateSolution(current *maze.Node, m *maze.Maze) []*maze.Node {
solution := make([]*maze.Node, 0, len(m.Nodes))
for current != m.Nodes[0] {
solution = append(solution, current)
current = s.parent[current]
}
solution = append(solution, m.Nodes[0])
for i, j := 0, len(solution)-1; i < j; i, j = i+1, j-1 {
solution[i], solution[j] = solution[j], solution[i]
}
return &maze.SolvedMaze{
Maze: m,
Solution: solution,
}
}
func (s *sorted_stack) insert(node *maze.Node, dists *map[*maze.Node]int) {
var dummy *maze.Node
*s = append(*s, dummy) // extend the slice
i, _ := slices.BinarySearchFunc(*s, node, func(e, t *maze.Node) int {
return (*dists)[t] - (*dists)[e]
})
copy((*s)[i+1:], (*s)[i:]) // make room
(*s)[i] = node
}
func (s *sorted_stack) pop() *maze.Node {
last_i := len(*s) - 1
ret := (*s)[last_i]
*s = (*s)[:last_i]
return ret
return solution
}

View File

@ -17,22 +17,34 @@ const (
_DFS = "dfs"
_BFS = "bfs"
_Dijkstra = "dijkstra"
_AStar = "a-star"
)
var TYPES = []string{
_DFS,
_BFS,
_Dijkstra,
_AStar,
}
func (f *SolverFactory) Get() Solver {
func (f *SolverFactory) Get(solved_chan chan<- *maze.SolvedMaze) Solver {
switch *f.Type {
case _DFS:
return &DFSSolver{}
return &DFSSolver{
solved_chan: solved_chan,
}
case _BFS:
return &BFSSolver{}
return &BFSSolver{
solved_chan: solved_chan,
}
case _AStar:
return &AStarSolver{
solved_chan: solved_chan,
}
case _Dijkstra:
return &DijkstraSolver{}
return &DijkstraSolver{
solved_chan: solved_chan,
}
}
panic(fmt.Sprintf("Unrecognized solver type %q", *f.Type))
}

27
solver/utils.go Normal file
View File

@ -0,0 +1,27 @@
package solver
import (
"maze-solver/maze"
"slices"
)
type sorted_stack []*maze.Node
func (s *sorted_stack) insert(node *maze.Node, weights *map[*maze.Node]int) {
var dummy *maze.Node
*s = append(*s, dummy) // extend the slice
i, _ := slices.BinarySearchFunc(*s, node, func(e, t *maze.Node) int {
return (*weights)[t] - (*weights)[e]
})
copy((*s)[i+1:], (*s)[i:]) // make room
(*s)[i] = node
}
func (s *sorted_stack) pop() *maze.Node {
last_i := len(*s) - 1
ret := (*s)[last_i]
*s = (*s)[:last_i]
return ret
}

63
visualizer/video.go Normal file
View File

@ -0,0 +1,63 @@
package visualizer
import (
"fmt"
"image/color"
"maze-solver/io/writer"
"maze-solver/maze"
"os"
"os/exec"
"path"
"github.com/mazznoer/colorgrad"
)
type VideoVisualizer struct {
Filename string
Framerate float64
ffmpeg_cmd string
}
func (v *VideoVisualizer) Init(*maze.Maze) {
path, err := exec.LookPath("ffmpeg")
if err != nil {
panic(err)
}
v.ffmpeg_cmd = path
}
func (v *VideoVisualizer) Run(lets_go chan<- bool) { lets_go <- true }
func (v *VideoVisualizer) Visualize(solved_chan <-chan *maze.SolvedMaze) {
tmp_dir, err := os.MkdirTemp("", "maze-solver-go-")
defer os.RemoveAll(tmp_dir)
if err != nil {
panic(err)
}
i := 0
for solved := range solved_chan {
img_writer := writer.ImageWriter{
Filename: path.Join(tmp_dir, fmt.Sprintf("%07v.png", i)),
Maze: solved,
CellWidth: 5,
CellHeight: 5,
WallColor: color.Black,
PathColor: color.White,
SolutionGradient: colorgrad.Warm(),
}
img_writer.Write()
i++
}
cmd := exec.Command(
v.ffmpeg_cmd,
"-y",
"-pattern_type", "glob",
"-i", path.Join(tmp_dir, "*.png"),
"-r", fmt.Sprint(int(v.Framerate)),
v.Filename,
)
err = cmd.Run()
if err != nil {
panic(err)
}
}

41
visualizer/visualizer.go Normal file
View File

@ -0,0 +1,41 @@
package visualizer
import (
"fmt"
"maze-solver/maze"
)
type Visualizer interface {
Init(*maze.Maze)
Visualize(<-chan *maze.SolvedMaze)
Run(lets_go chan<- bool)
}
type VisualizerFactory struct {
Type *string
Filename *string
Framerate *float64
}
const (
_VIDEO = "video"
_WINDOW = "window"
)
var VIZ_METHODS = []string{
_VIDEO,
_WINDOW,
}
func (f *VisualizerFactory) Get() Visualizer {
switch *f.Type {
case _VIDEO:
return &VideoVisualizer{
Filename: *f.Filename,
Framerate: *f.Framerate,
}
case _WINDOW:
return &WindowVisualizer{}
}
panic(fmt.Sprintf("Unrecognized visualizer type %q", *f.Type))
}

65
visualizer/window.go Normal file
View File

@ -0,0 +1,65 @@
package visualizer
import (
"image/color"
"maze-solver/io/writer"
"maze-solver/maze"
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/canvas"
"fyne.io/fyne/dialog"
"github.com/mazznoer/colorgrad"
)
type WindowVisualizer struct {
app fyne.App
window fyne.Window
img_writer writer.ImageWriter
cimg *canvas.Image
}
func (v *WindowVisualizer) Init(m *maze.Maze) {
v.app = app.New()
v.window = v.app.NewWindow("maze-solver-go")
v.img_writer = writer.ImageWriter{
Filename: "",
Maze: &maze.SolvedMaze{
Maze: m,
Solution: []*maze.Node{},
},
CellWidth: 2,
CellHeight: 2,
WallColor: color.Black,
PathColor: color.White,
SolutionGradient: colorgrad.Warm(),
}
v.cimg = canvas.NewImageFromImage(v.img_writer.GenerateImage())
v.window.SetContent(v.cimg)
v.window.Resize(
fyne.NewSize(
m.Width*v.img_writer.CellWidth,
m.Height*v.img_writer.CellHeight,
),
)
v.window.Show()
}
func (v *WindowVisualizer) Visualize(solved_chan <-chan *maze.SolvedMaze) {
for solved := range solved_chan {
v.img_writer.Maze = solved
v.cimg.Image = v.img_writer.GenerateImage()
v.cimg.Refresh()
}
}
func (v *WindowVisualizer) Run(lets_go chan<- bool) {
dial := dialog.NewConfirm("Start", "Let's go", func(ok bool) {
lets_go <- ok
if !ok {
v.window.Close()
}
}, v.window)
dial.Show()
v.window.ShowAndRun()
}