- Favicon: 🚀 emoji via inline SVG data URI, both sokoban.html and php/index.php
- api.php: mb_substr() -> substr() (mbstring not enabled on the server)
- api.php: MariaDB rejects LIMIT directly inside NOT IN(...) (error 1235) —
wrap the top-5 subquery in an extra derived-table SELECT
Verified live against the deployed MySQL/MariaDB DB: whoami, set_name,
submit (insert + dedup + trim-to-5), top all round-trip correctly.
2019 lines
49 KiB
HTML
2019 lines
49 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Sokoban — 60 Levels</title>
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚀</text></svg>">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { background: #0f0f1a; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: 'Courier New', monospace; }
|
||
#container { display: flex; flex-direction: column; align-items: center; gap: 10px; }
|
||
canvas { border: 3px solid #e94560; border-radius: 12px; box-shadow: 0 0 20px rgba(233,69,96,0.4); }
|
||
#hud { display: flex; gap: 20px; color: #e94560; font-size: 16px; font-weight: bold; }
|
||
#hud span { background: rgba(233,69,96,0.1); padding: 8px 16px; border-radius: 20px; border: 1px solid rgba(233,69,96,0.3); }
|
||
#controls { color: #aaa; font-size: 13px; }
|
||
kbd { background: #1a1a2e; border: 1px solid #e94560; border-radius: 4px; padding: 2px 6px; color: #e94560; }
|
||
#solve-overlay { position: fixed; inset: 0; background: rgba(10,10,21,0.75); display: flex; justify-content: center; align-items: center; z-index: 10; }
|
||
#solve-overlay.hidden { display: none; }
|
||
#solve-box { background: #1a1a2e; border: 2px solid #e94560; border-radius: 12px; padding: 24px 32px; text-align: center; box-shadow: 0 0 30px rgba(233,69,96,0.5); }
|
||
#solve-box p { color: #e94560; font-size: 16px; font-weight: bold; margin-bottom: 8px; }
|
||
#solve-timer { color: #48cae4; font-size: 22px; margin-bottom: 16px; }
|
||
#solve-cancel { background: #e94560; color: #fff; border: none; border-radius: 8px; padding: 8px 20px; font: inherit; font-weight: bold; cursor: pointer; }
|
||
#solve-cancel:hover { background: #ff6b8a; }
|
||
#solve-cancel.solve-found { background: #7CFF6B; color: #0a0a15; }
|
||
#solve-cancel.solve-found:hover { background: #9dff8f; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="container">
|
||
<canvas id="game"></canvas>
|
||
<div id="hud">
|
||
<span id="level-name">Уровень 1</span>
|
||
<span id="box-count">📦 0/0</span>
|
||
<span id="moves-count">👣 0</span>
|
||
</div>
|
||
<div id="controls">
|
||
<kbd>← → ↑ ↓</kbd> или <kbd>WASD</kbd> — движение
|
||
<kbd>N</kbd> — след.
|
||
<kbd>P</kbd> — пред.
|
||
<kbd>R</kbd> — рестарт
|
||
<kbd>F</kbd> — автопоиск (<kbd>Esc</kbd> — отмена)
|
||
<kbd>G</kbd> — лучшее сохранённое решение
|
||
</div>
|
||
</div>
|
||
|
||
<div id="solve-overlay" class="hidden">
|
||
<div id="solve-box">
|
||
<p>🤖 ИИ ищет решение… Ожидайте.</p>
|
||
<div id="solve-timer">0.0с</div>
|
||
<button id="solve-cancel">Отмена</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Вставь ВЕСЬ дамп между тегами -->
|
||
<script type="text/plain" id="levels-data">
|
||
*************************************
|
||
Maze: 1
|
||
File offset: 148C, DS:00FC, table offset: 0000
|
||
Size X: 22
|
||
Size Y: 11
|
||
End: 14BD
|
||
Length: 50
|
||
|
||
XXXXX
|
||
X X
|
||
X* X
|
||
XXX *XXX
|
||
X * * X
|
||
XXX X XXX X XXXXXX
|
||
X X XXX XXXXXXX ..X
|
||
X * * ..X
|
||
XXXXX XXXX X@XXXX ..X
|
||
X XXX XXXXXX
|
||
XXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 2
|
||
File offset: 14BE, DS:012E, table offset: 0032
|
||
Size X: 14
|
||
Size Y: 10
|
||
End: 14E8
|
||
Length: 43
|
||
|
||
XXXXXXXXXXXX
|
||
X.. X XXX
|
||
X.. X * * X
|
||
X.. X*XXXX X
|
||
X.. @ XX X
|
||
X.. X X * XX
|
||
XXXXXX XX* * X
|
||
X * * * * X
|
||
X X X
|
||
XXXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 3
|
||
File offset: 14E9, DS:0159, table offset: 005D
|
||
Size X: 17
|
||
Size Y: 10
|
||
End: 150D
|
||
Length: 37
|
||
|
||
XXXXXXXX
|
||
X @X
|
||
X *X* XX
|
||
X * *X
|
||
XX* * X
|
||
XXXXXXXXX * X XXX
|
||
X.... XX * * X
|
||
XX... * * X
|
||
X.... XXXXXXXXXX
|
||
XXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 4
|
||
File offset: 150E, DS:017E, table offset: 0082
|
||
Size X: 22
|
||
Size Y: 13
|
||
End: 154D
|
||
Length: 64
|
||
|
||
XXXXXXXX
|
||
X ....X
|
||
XXXXXXXXXXXX ....X
|
||
X X * * ....X
|
||
X ***X* * X ....X
|
||
X * * X ....X
|
||
X ** X* * *XXXXXXXX
|
||
XXXX * X X
|
||
X X XXXXXXXXX
|
||
X * XX
|
||
X **X** @X
|
||
X X XX
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 5
|
||
File offset: 154E, DS:01BE, table offset: 00C2
|
||
Size X: 17
|
||
Size Y: 13
|
||
End: 1580
|
||
Length: 51
|
||
|
||
XXXXX
|
||
X XXXXX
|
||
X X*XX X
|
||
X * X
|
||
XXXXXXXXX XXX X
|
||
X.... XX * *XXX
|
||
X.... * ** XX
|
||
X.... XX* * @X
|
||
XXXXXXXXX * XX
|
||
X * * X
|
||
XXX XX X
|
||
X X
|
||
XXXXXX
|
||
|
||
*************************************
|
||
Maze: 6
|
||
File offset: 1581, DS:01F1, table offset: 00F5
|
||
Size X: 12
|
||
Size Y: 11
|
||
End: 15AC
|
||
Length: 44
|
||
|
||
XXXXXX XXX
|
||
X.. X XX@XX
|
||
X.. XXX X
|
||
X.. ** X
|
||
X.. X X * X
|
||
X..XXX X * X
|
||
XXXX * X* X
|
||
X *X * X
|
||
X * * X
|
||
X XX X
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 7
|
||
File offset: 15AD, DS:021D, table offset: 0121
|
||
Size X: 13
|
||
Size Y: 12
|
||
End: 15D8
|
||
Length: 44
|
||
|
||
XXXXX
|
||
XXXXXXX XX
|
||
XX X @XX ** X
|
||
X * X
|
||
X * XXX X
|
||
XXX XXXXX*XXX
|
||
X * XXX ..X
|
||
X * * * ...X
|
||
X XXX...X
|
||
X ** X X...X
|
||
X XXX XXXXX
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 8
|
||
File offset: 15D9, DS:0249, table offset: 014D
|
||
Size X: 16
|
||
Size Y: 17
|
||
End: 161A
|
||
Length: 66
|
||
|
||
XXXX
|
||
X XXXXXXXXXXX
|
||
X * * * X
|
||
X *X * X * X
|
||
X * * X X
|
||
XXX *X X XXXX X
|
||
X@X* * * XX X
|
||
X * X*X X X
|
||
XX * * * * X
|
||
XXXX XXXXXXXXX
|
||
XXX XXX
|
||
X X
|
||
X X
|
||
X......X
|
||
X......X
|
||
X......X
|
||
XXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 9
|
||
File offset: 161B, DS:028B, table offset: 018F
|
||
Size X: 17
|
||
Size Y: 18
|
||
End: 165D
|
||
Length: 67
|
||
|
||
XXXXXXX
|
||
X ...X
|
||
XXXXX ...X
|
||
X ...X
|
||
X XX ...X
|
||
XX XX ...X
|
||
XXX XXXXXXXX
|
||
X *** XX
|
||
XXXXX * * XXXXX
|
||
XX X* * X X
|
||
X@ * * * * X
|
||
XXXXXX ** * XXXXX
|
||
X * X
|
||
XXXX XXX
|
||
X X
|
||
X X
|
||
X X
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 10
|
||
File offset: 165E, DS:02CE, table offset: 01D2
|
||
Size X: 21
|
||
Size Y: 20
|
||
End: 16BE
|
||
Length: 97
|
||
|
||
XXXX
|
||
XXXXXX X
|
||
X X
|
||
X XXXX XXX
|
||
XXX XXXXX XXX X
|
||
XX@XXXX *** X X
|
||
X ** ** * X....XX
|
||
X ***X * X.....X
|
||
X * X ** ** X.....X
|
||
XXX X * X.....X
|
||
X X * * * X.....X
|
||
X XXXXXXX XXX.....X
|
||
X X * * X.....X
|
||
XXX X ** * *XXXXXXX
|
||
X X * X
|
||
X X *** *** X
|
||
X X X X
|
||
X XXXXXXXXX X
|
||
X X
|
||
XXXXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 11
|
||
File offset: 16BF, DS:032F, table offset: 0233
|
||
Size X: 19
|
||
Size Y: 15
|
||
End: 16FF
|
||
Length: 65
|
||
|
||
XXXX
|
||
XXXX X X
|
||
XXX XXX* X
|
||
XX @ * X
|
||
XX * **XX XX
|
||
X X*XX X
|
||
X X * ** X XXX
|
||
X * X X * XXXXX
|
||
XXXX X ** X X
|
||
XXXX XX * X
|
||
X. XXX XXXXXXXX
|
||
X.. ..X XXXX
|
||
X...X.X
|
||
X.....X
|
||
XXXXXXX
|
||
|
||
*************************************
|
||
Maze: 12
|
||
File offset: 1700, DS:0370, table offset: 0274
|
||
Size X: 13
|
||
Size Y: 16
|
||
End: 1741
|
||
Length: 66
|
||
|
||
XXXXXXXXX
|
||
X&.&X&.&X
|
||
X.&.&.&.X
|
||
X&.&.&.&X
|
||
X.&.&.&.X
|
||
X&.&.&.&X
|
||
XXX XXX
|
||
X X
|
||
XXXXXX XXXXXX
|
||
X X
|
||
X * * * * * X
|
||
XX * * * * XX
|
||
X* * * * *X
|
||
X *@* X
|
||
X XXXXX X
|
||
XXXX XXXX
|
||
|
||
*************************************
|
||
Maze: 13
|
||
File offset: 1742, DS:03B2, table offset: 02B6
|
||
Size X: 20
|
||
Size Y: 13
|
||
End: 178D
|
||
Length: 76
|
||
|
||
XXXXXXXXX
|
||
XXX XX XXXXX
|
||
XXX X X XXXX
|
||
X ** X* X X ... X
|
||
X X *X@*XX X X.X. X
|
||
X XX X* X ... X
|
||
X *X * X X X.X. X
|
||
X XX XX* * ... X
|
||
X * XX X X*X.X. X
|
||
XX ** * * *... X
|
||
X* XXXXXX XX X
|
||
X X XXXXXXXXXX
|
||
XXXXX
|
||
|
||
*************************************
|
||
Maze: 14
|
||
File offset: 178E, DS:03FE, table offset: 0302
|
||
Size X: 17
|
||
Size Y: 13
|
||
End: 17C7
|
||
Length: 58
|
||
|
||
XXXXXXXXXXXXXXXX
|
||
X X
|
||
X X XXXXXX X
|
||
X X * * * *X X
|
||
X X *@* XX XX
|
||
X X X* * *XXX...X
|
||
X X * * XX...X
|
||
X XXX*** * XX...X
|
||
X X XX XX...X
|
||
XXXXX XX XX...X
|
||
XXXXX XXX
|
||
X X
|
||
XXXXXXX
|
||
|
||
*************************************
|
||
Maze: 15
|
||
File offset: 17C8, DS:0438, table offset: 033C
|
||
Size X: 17
|
||
Size Y: 17
|
||
End: 180F
|
||
Length: 72
|
||
|
||
XXXX
|
||
XXXX X
|
||
XX X X
|
||
X * * X
|
||
XXX X* XXXX
|
||
X * XX* X
|
||
X X @ * X *X
|
||
X X * XXXX
|
||
XX XXXX*XX X
|
||
X *X.....X X X
|
||
X *...&. *X XXX
|
||
XX X.....X X
|
||
X XXX XXXXXXX
|
||
X ** X X
|
||
X X X
|
||
XXXXXX X
|
||
XXXXX
|
||
|
||
*************************************
|
||
Maze: 16
|
||
File offset: 1810, DS:0480, table offset: 0384
|
||
Size X: 14
|
||
Size Y: 15
|
||
End: 184E
|
||
Length: 63
|
||
|
||
XXXXX
|
||
X XX
|
||
X X XXXX
|
||
X * XXXX X
|
||
X ** * *X
|
||
XXX@ X* XX
|
||
X XX * * XX
|
||
X * XX XX .X
|
||
X X*XX* X.X
|
||
XXX *..XX.X
|
||
X X.&...X
|
||
X ** X.....X
|
||
X XXXXXXXXX
|
||
X X
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 17
|
||
File offset: 184F, DS:04BF, table offset: 03C3
|
||
Size X: 18
|
||
Size Y: 16
|
||
End: 1895
|
||
Length: 71
|
||
|
||
XXXXXXX
|
||
XXXXXXX X
|
||
X X *@* X
|
||
X** X XXXXXXXXX
|
||
X XXX......XX X
|
||
X *......XX X X
|
||
X XXX...... X
|
||
XX XXXX XXX X*XX
|
||
X X* X * X X
|
||
X * *** X *XX X
|
||
X * * XXX** X X
|
||
XXXXX * X X
|
||
XXX XXX X X
|
||
X X X
|
||
XXXXXXXX X
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 18
|
||
File offset: 1896, DS:0506, table offset: 040A
|
||
Size X: 22
|
||
Size Y: 13
|
||
End: 18DC
|
||
Length: 71
|
||
|
||
XXXXXXXXXXXX
|
||
X . XX X
|
||
X X. @ X
|
||
XXXXXX XX...X XXXX
|
||
XX XX...XXXX XXXX
|
||
X * XX... * X * X
|
||
X .. XX X XX XX X
|
||
XXXX*XXX*X * X X XX
|
||
XXX X XX* ** X X
|
||
X ** X X * X *XX X
|
||
X X
|
||
XXXXXXXXXXXXXXXXX X
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 19
|
||
File offset: 18DD, DS:054D, table offset: 0451
|
||
Size X: 28
|
||
Size Y: 20
|
||
End: 1951
|
||
Length: 117
|
||
|
||
XXXXXX
|
||
X @XXXX
|
||
XXXXX * X
|
||
X XX XXXX
|
||
X *XX XX X
|
||
X X XXXXX X
|
||
X X** * X X
|
||
X * * XXX X X
|
||
X X * X X X
|
||
X X X*X X X
|
||
XX XXXX X X X
|
||
X * XXXXX X X XXXX
|
||
XX * * XXX XXXX
|
||
XXXXX XXX * *X * X .....X
|
||
X XX X XX X.....X
|
||
X **** XXXXXX*XX X.XX.X
|
||
XX XX X....X
|
||
XX XXXXXXXXXXXXXXX ....X
|
||
X X XXXXX XX
|
||
XXXX XXXX
|
||
|
||
*************************************
|
||
Maze: 20
|
||
File offset: 1952, DS:05C2, table offset: 04C6
|
||
Size X: 20
|
||
Size Y: 20
|
||
End: 19B7
|
||
Length: 102
|
||
|
||
XXXXXXXXXXXX
|
||
X..........X
|
||
XXX.X.X.X.X..X
|
||
X .........X
|
||
X@ * * * &.&.X
|
||
XXXXXXX XXXXXXX
|
||
XXXX X XX X
|
||
XX * X X * XX
|
||
X X*X XXX XXX* XX
|
||
X * * * X * * * X
|
||
X X * XX X* X
|
||
X *XXXX*XXXX*XX X
|
||
XXXX XX X X X
|
||
X* XX X X ** X
|
||
X X * X * X
|
||
XXX X ** X * XXX
|
||
X X X * XX
|
||
X XXXXXXXX X
|
||
X X
|
||
XXXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 21
|
||
File offset: 19B8, DS:0628, table offset: 052C
|
||
Size X: 16
|
||
Size Y: 14
|
||
End: 19F1
|
||
Length: 58
|
||
|
||
XXXXXXXXXX
|
||
X.. X X
|
||
X.. X
|
||
X.. X XXXX
|
||
XXXXXXX X XX
|
||
X X
|
||
X X XX X X
|
||
XXXX XX XXXX XX
|
||
X * XXXXX X X
|
||
X X * * X * X
|
||
X @* * X XX
|
||
XXXX XX XXXXXXX
|
||
X X
|
||
XXXXXX
|
||
|
||
*************************************
|
||
Maze: 22
|
||
File offset: 19F2, DS:0662, table offset: 0566
|
||
Size X: 22
|
||
Size Y: 20
|
||
End: 1A66
|
||
Length: 117
|
||
|
||
XXXX
|
||
XXXXXXXXXXXX XXXXX
|
||
X X X * X XX
|
||
X * * * * X * * X
|
||
XX* * X @X * * X
|
||
XXX XXXXXXXXXXXX XX
|
||
X * *X X......X *X
|
||
X X X X......XX X
|
||
X XX XX X .....X X
|
||
X X *...... * X
|
||
X X * XX X......X X
|
||
X * *X X......X *X
|
||
X * X XX*XXXXX X
|
||
X * * XXXX * * * *X
|
||
XX X * * * * XXX
|
||
X XXXXXX * * X
|
||
X X XXXXXXX X
|
||
XXXXXXX X* X
|
||
X XXXXXXXXXXX
|
||
XXXXX
|
||
|
||
*************************************
|
||
Maze: 23
|
||
File offset: 1A67, DS:06D7, table offset: 05DB
|
||
Size X: 25
|
||
Size Y: 14
|
||
End: 1AB7
|
||
Length: 81
|
||
|
||
XXXXXXX
|
||
X X XXXX
|
||
X *X* X XX
|
||
XXXXXXXX X X XXXXXXXX
|
||
X.... X *X* X *X X X
|
||
X....X X X* X X
|
||
X..X. *X X * X* X
|
||
X... @XX X* X* X X X
|
||
X.... XX *X *XXXXXXXX
|
||
XXXXXXXX X**X* X
|
||
X *X X *X
|
||
X X X X
|
||
XXXX XXXXX
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 24
|
||
File offset: 1AB8, DS:0728, table offset: 062C
|
||
Size X: 21
|
||
Size Y: 19
|
||
End: 1B1F
|
||
Length: 104
|
||
|
||
XXXXXXXXXX
|
||
X........XXXX
|
||
X.X.X....X X
|
||
X........** X
|
||
X .XXX XXXX
|
||
XXXXXXXXX * X X
|
||
X * * * * X
|
||
X X X * *X X
|
||
XX XXXXX X X X
|
||
X * X XXXX X
|
||
XX *X X XX X X
|
||
X XX*XXX X XX
|
||
X * * X X X X
|
||
XXXXX X XX X XX XX
|
||
X*X X * * * X
|
||
X@X *X*** X X
|
||
XXX * XXXXX
|
||
XX X X X
|
||
XXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 25
|
||
File offset: 1B20, DS:0790, table offset: 0694
|
||
Size X: 23
|
||
Size Y: 17
|
||
End: 1B7C
|
||
Length: 93
|
||
|
||
XXXX
|
||
XXXXXX XXXXX
|
||
XXXXXXX X X
|
||
X * * XX X X X
|
||
X XXXX * X .X
|
||
X * X X XX.X.X
|
||
XX*XXXX* * * XX.X.X
|
||
X X XXXX.XXX
|
||
X * XXXXXX X.X.X
|
||
XXXXXX***XX @X.X.X
|
||
X X X*X*XXX. .X
|
||
X XXXX X***** X ...X
|
||
X X * X X ...X
|
||
X X XX XX XXX...X
|
||
X XXXXXX*XXXXXX XXXXXX
|
||
X X X X
|
||
XXXXXXXXXX XXXX
|
||
|
||
*************************************
|
||
Maze: 26
|
||
File offset: 1B7D, DS:07ED, table offset: 06F1
|
||
Size X: 15
|
||
Size Y: 15
|
||
End: 1BBB
|
||
Length: 63
|
||
|
||
XXXXXXXXX
|
||
X X
|
||
X XXXX
|
||
XX XXXX X X
|
||
XX X@XX X
|
||
X *** * **X
|
||
X X XX * X
|
||
X X XX * XXXX
|
||
XXXX *** *X X
|
||
X XX ....X
|
||
X X X X.. .X
|
||
X X X XX...X
|
||
XXXXX * X...X
|
||
XX XXXXX
|
||
XXXXX
|
||
|
||
*************************************
|
||
Maze: 27
|
||
File offset: 1BBC, DS:082C, table offset: 0730
|
||
Size X: 23
|
||
Size Y: 13
|
||
End: 1C0B
|
||
Length: 80
|
||
|
||
XXXXXXXXXXXXXXXXX
|
||
X... X X XXX
|
||
XX..... *XX X X * X
|
||
X......X * X * X
|
||
X......X X X X X XX
|
||
XXXXXXXXX * * X X XXX
|
||
X X*XX* XX XX X
|
||
XX * X * * X X
|
||
X XX XXX X XXXXX*X X
|
||
X * ** * * X
|
||
X * *XX* XXXXXXXX X
|
||
XXXXXXX @ XX XXX
|
||
XXXXXX
|
||
|
||
*************************************
|
||
Maze: 28
|
||
File offset: 1C0C, DS:087C, table offset: 0780
|
||
Size X: 15
|
||
Size Y: 17
|
||
End: 1C54
|
||
Length: 73
|
||
|
||
XXXXXXX
|
||
X@ X X
|
||
X * X
|
||
XXX XX X
|
||
XXXX * X XX
|
||
X X XX
|
||
X * *XXXX * X
|
||
X ** X X *X
|
||
X* * X* X
|
||
XX **X ** XX
|
||
X ** X X * X
|
||
X XXXX * X
|
||
X X*XX..XX X
|
||
XXX .X....XXXXX
|
||
X .......XX
|
||
X.... ..X
|
||
XXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 29
|
||
File offset: 1C55, DS:08C5, table offset: 07C9
|
||
Size X: 24
|
||
Size Y: 11
|
||
End: 1C96
|
||
Length: 66
|
||
|
||
XXXXX
|
||
XXXXXX XXX XXXX
|
||
XXXXX XXX * * * X
|
||
XXXX XX X* * * X X
|
||
X.... ** * * * X*XX
|
||
X.. X XX X XXX*XX X X
|
||
X.... X XXX X X
|
||
X.... X XX * XXX* X
|
||
X..XXXXXX * X XXXX XX
|
||
XXXX X XXX @ X
|
||
XXXXXXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 30
|
||
File offset: 1C97, DS:0907, table offset: 080B
|
||
Size X: 14
|
||
Size Y: 20
|
||
End: 1CE3
|
||
Length: 77
|
||
|
||
XXXXX
|
||
X XXXXXXX
|
||
X * XXX X
|
||
X * ** X
|
||
XX XXXX X
|
||
XXX X X XXX
|
||
X X X@XX
|
||
X ** * X
|
||
X X X * XXXX
|
||
XXXXX X X X
|
||
X *XXXX X
|
||
X * * X
|
||
XX XXXXX XX
|
||
XXXXXXXXXX X
|
||
XX....X * * X
|
||
X.....X **X X
|
||
X.. ..X * * X
|
||
X.....* X X
|
||
XX XXXXXXXXXX
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 31
|
||
File offset: 1CE4, DS:0954, table offset: 0858
|
||
Size X: 15
|
||
Size Y: 12
|
||
End: 1D16
|
||
Length: 51
|
||
|
||
XXXXXXX
|
||
X X XXXXX
|
||
XX X X...XXX
|
||
X *X X... X
|
||
X * X** ... X
|
||
X *X X... .X
|
||
X X *XXXXXXXX
|
||
XX* * * X
|
||
XX X ** X X
|
||
XXXXXX XX**@X
|
||
X XX
|
||
XXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 32
|
||
File offset: 1D17, DS:0987, table offset: 088B
|
||
Size X: 18
|
||
Size Y: 16
|
||
End: 1D60
|
||
Length: 74
|
||
|
||
XXXX
|
||
X XXXXXXXXX
|
||
XX XX @X X
|
||
X *X * * XXXX
|
||
X* * X * *X XX
|
||
XX *XX X* * X
|
||
X X X X *** X
|
||
X * * *XX XXXX
|
||
X * * X*X X X
|
||
XX XXX XXX* X
|
||
X X.... X
|
||
XXXX......XXXX
|
||
X....XXXX
|
||
X...XX
|
||
X...X
|
||
XXXXX
|
||
|
||
*************************************
|
||
Maze: 33
|
||
File offset: 1D61, DS:09D1, table offset: 08D5
|
||
Size X: 13
|
||
Size Y: 15
|
||
End: 1D97
|
||
Length: 55
|
||
|
||
XXXX
|
||
XXXXX X
|
||
XX *X
|
||
XX * XX XXX
|
||
X@* * X * X
|
||
XXXX XX *X
|
||
X....X* * X
|
||
X....X *X
|
||
X.... ** XX
|
||
X... X * X
|
||
XXXXXX* * X
|
||
X XXX
|
||
X* XXX
|
||
X X
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 34
|
||
File offset: 1D98, DS:0A08, table offset: 090C
|
||
Size X: 12
|
||
Size Y: 15
|
||
End: 1DCB
|
||
Length: 52
|
||
|
||
XXXXXXXXXXXX
|
||
XX XX X
|
||
XX * * X
|
||
XXXX XX ** X
|
||
X * X X
|
||
X *** X XXXX
|
||
X X X * XX
|
||
X X X * X
|
||
X *X *X X
|
||
X ..X XXXX
|
||
XXXX.. * X@X
|
||
X.....X *X X
|
||
XX....X * X
|
||
XXX..XX X
|
||
XXXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 35
|
||
File offset: 1DCC, DS:0A3C, table offset: 0940
|
||
Size X: 20
|
||
Size Y: 16
|
||
End: 1E20
|
||
Length: 85
|
||
|
||
XXXXXXXXXXXX XXXXXX
|
||
X X X@XXXX....X
|
||
X **X .....X
|
||
X X XXX XX ....X
|
||
XX XX XXX X ....X
|
||
X * * X XX XXXX
|
||
X * *XX X X
|
||
XXXX X XXXX XX XX X
|
||
X X X* XX XX X
|
||
X * * X XX XXXXXXX
|
||
X X * * X X
|
||
X * XX XX X X
|
||
X ** ** X
|
||
XX XX XXX * X
|
||
X X X X
|
||
XXXXXX XXXXXX
|
||
|
||
*************************************
|
||
Maze: 36
|
||
File offset: 1E21, DS:0A91, table offset: 0995
|
||
Size X: 18
|
||
Size Y: 19
|
||
End: 1E7E
|
||
Length: 94
|
||
|
||
XXXX
|
||
XXX XX
|
||
XXXX * X
|
||
X * * XXXX
|
||
X * X * X XXXX
|
||
X X X * X X..X
|
||
XX*X* XXXX*XXXX..X
|
||
X XXXXX XX ...X
|
||
X*X XX@XX XX ..X
|
||
X X * ...X
|
||
X XXXX XXX ..X
|
||
XXX XX X XX ...X
|
||
XX* XXXX* XXX..X
|
||
X XX X X..X
|
||
XX **XX * X XXXX
|
||
X **** X
|
||
X * XXX X
|
||
X X XXXXXX
|
||
XXXXX
|
||
|
||
*************************************
|
||
Maze: 37
|
||
File offset: 1E7F, DS:0AEF, table offset: 09F3
|
||
Size X: 21
|
||
Size Y: 15
|
||
End: 1ED0
|
||
Length: 82
|
||
|
||
XXXXXXXXXXX
|
||
X...... XXXXXXXXX
|
||
X...... X XX X
|
||
X..XXX * * X
|
||
X... * * X XXX X
|
||
X...X*XXXXX X X
|
||
XXX X X* X *XXX
|
||
X ** * * *XX * X
|
||
X * X*X XX X
|
||
XXX XX X * XXXXXXX
|
||
X * * XX XX
|
||
X * * X
|
||
XX X X X
|
||
XXXXX@XXXXX
|
||
XXX
|
||
|
||
*************************************
|
||
Maze: 38
|
||
File offset: 1ED1, DS:0B41, table offset: 0A45
|
||
Size X: 14
|
||
Size Y: 15
|
||
End: 1F11
|
||
Length: 65
|
||
|
||
XXXXXXXXX
|
||
X.... XX
|
||
X.X.X * XX
|
||
XX....X X @XX
|
||
X ....X X XX
|
||
X X* XX* X
|
||
XX XXX * X
|
||
X* * * *X X
|
||
X X * * XX X
|
||
X XXX XX X
|
||
X XX XX XX
|
||
X * X * X
|
||
XXX* * XXX
|
||
X XXXXX
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 39
|
||
File offset: 1F12, DS:0B82, table offset: 0A86
|
||
Size X: 23
|
||
Size Y: 18
|
||
End: 1F73
|
||
Length: 98
|
||
|
||
XXX
|
||
XX.XXX
|
||
X....X
|
||
XXXXXXXXXXXXX....X
|
||
XX XX XX....XXXXX
|
||
X **XX * @XX.... X
|
||
X ** *X ....X X
|
||
X * XX ** X X....X XX
|
||
X * XX * X XX XXX X
|
||
XX XXXXX XXX X
|
||
XX * * XXXXX XXX X
|
||
X *XXX X XXXXX X XXXX
|
||
X * X X
|
||
X * X* * *XXX X
|
||
X ***X * X XXXX
|
||
X X ** X
|
||
XXXXXX XXX
|
||
XXXXX
|
||
|
||
*************************************
|
||
Maze: 40
|
||
File offset: 1F74, DS:0BE4, table offset: 0AE8
|
||
Size X: 11
|
||
Size Y: 11
|
||
End: 1F9A
|
||
Length: 39
|
||
|
||
XXXX
|
||
XXXXXXX @X
|
||
X * X
|
||
X *XX *X
|
||
XX*X...X X
|
||
X *... X
|
||
X X. .X XX
|
||
X X X* X
|
||
X* * X
|
||
X XXXXXXX
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 41
|
||
File offset: 1F9B, DS:0C0B, table offset: 0B0F
|
||
Size X: 20
|
||
Size Y: 15
|
||
End: 1FDE
|
||
Length: 68
|
||
|
||
XXXXX
|
||
XX XX
|
||
XX X
|
||
XX ** X
|
||
XX ** * X
|
||
X * * X
|
||
XXXX X ** XXXXX
|
||
X XXXXXXXX XX X
|
||
X.. ***@X
|
||
X.X XXXXXXX XX XX
|
||
X.X XXXXXXX. X* *XXX
|
||
X........... X * X
|
||
XXXXXXXXXXXXXX * X
|
||
XX XXX
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 42
|
||
File offset: 1FDF, DS:0C4F, table offset: 0B53
|
||
Size X: 13
|
||
Size Y: 18
|
||
End: 2022
|
||
Length: 68
|
||
|
||
XXXXXXXX
|
||
X@XX XXXX
|
||
X * * X
|
||
X * * ***X
|
||
X **X X X
|
||
XX* * X
|
||
X * *****XX
|
||
X *XXXX X X
|
||
X *....X X
|
||
X XX....X** X
|
||
X XX.... XX
|
||
X ....X X
|
||
XX X....X**X
|
||
X X....X X
|
||
X X
|
||
XXXX XX*XXX
|
||
X X
|
||
XXXXXX
|
||
|
||
*************************************
|
||
Maze: 43
|
||
File offset: 2023, DS:0C93, table offset: 0B97
|
||
Size X: 17
|
||
Size Y: 16
|
||
End: 206B
|
||
Length: 73
|
||
|
||
XXXXXXXXXXXX
|
||
X XX
|
||
X X X** * X
|
||
X* X*X XX @X
|
||
XX XX X * X XX
|
||
X * X* X X
|
||
X X * X X
|
||
XX * * XX X
|
||
X X XX * X
|
||
X XX **X X
|
||
XXXXXX** X X
|
||
X....X XXXXXXXX
|
||
X.X... XX
|
||
X.... X
|
||
X.... X
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 44
|
||
File offset: 206C, DS:0CDC, table offset: 0BE0
|
||
Size X: 25
|
||
Size Y: 19
|
||
End: 20DD
|
||
Length: 114
|
||
|
||
XXXXXX
|
||
XXXXX X
|
||
X X X XXXXX
|
||
X * X * XXXXXX
|
||
XX* XXX XX X
|
||
XXX ** * * X XX XXXXX
|
||
X * XXXXXX XX X
|
||
X XXXXXXXX X@ X X X X
|
||
XX XXX XXXX X*X X X
|
||
X XXX XXXX XX.. X * XX
|
||
X * * X*XX.. X*XX XX
|
||
X X X X ..XX XX * X
|
||
XXXX X XX X..X * X
|
||
XXXXX X..X X X XX
|
||
XXXXXX..X X XX
|
||
X..XXXXX X
|
||
X.. X
|
||
XX XXX XX
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 45
|
||
File offset: 20DE, DS:0D4E, table offset: 0C52
|
||
Size X: 19
|
||
Size Y: 11
|
||
End: 2114
|
||
Length: 55
|
||
|
||
XXXXXXX
|
||
XXXXX X XXXX
|
||
X X * X
|
||
XXXX X** XX XX X
|
||
XX X X XX XXX
|
||
X XXX *X* * * X
|
||
X... X XX X X
|
||
X...X @ X XXX XX
|
||
X...X XXX * * X
|
||
XXXXXXXX XX X X
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 46
|
||
File offset: 2115, DS:0D85, table offset: 0C89
|
||
Size X: 22
|
||
Size Y: 17
|
||
End: 2173
|
||
Length: 95
|
||
|
||
XXXXXXXXX XXXX
|
||
X XX XXXX X
|
||
X * X * X
|
||
X X XX X XXXX
|
||
XX * * **X X X
|
||
XXXX X X * * X
|
||
XXXXX XXXX XXX...X
|
||
X X* X X XXXX.....X
|
||
X X X X XX.....X
|
||
XXXXXX X X* XXX...X
|
||
X XX X *X X...X
|
||
XX * *X XXXXX
|
||
XX ***XX X * X
|
||
X X X XXX XXX
|
||
X * X* @XXXX
|
||
XXXXX X X
|
||
XXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 47
|
||
File offset: 2174, DS:0DE4, table offset: 0CE8
|
||
Size X: 19
|
||
Size Y: 15
|
||
End: 21B5
|
||
Length: 66
|
||
|
||
XXXXX
|
||
X X
|
||
X X XXXXXX
|
||
X *@XXXXXX
|
||
X * XX* XXX X
|
||
X XXXX * * X
|
||
X XXXXX X X* XXXX
|
||
XX XXXX XX* X
|
||
X *X * X XX XX X
|
||
X X X...X X
|
||
XXXXXX XXX ... X
|
||
XXXX X X...X X
|
||
X XXX X X
|
||
X X
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 48
|
||
File offset: 21B6, DS:0E26, table offset: 0D2A
|
||
Size X: 16
|
||
Size Y: 15
|
||
End: 21F4
|
||
Length: 63
|
||
|
||
XXXX
|
||
X XX
|
||
X XX
|
||
X ** XX
|
||
XXX* * XX
|
||
XXXX * X
|
||
XXX X XXXXX X
|
||
X X X....* X
|
||
X X * ....X X
|
||
X * X X.&..X X
|
||
XXX XXXX XXX X
|
||
XXXX @* XX*XX
|
||
XXX * X
|
||
X XX X
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 49
|
||
File offset: 21F5, DS:0E65, table offset: 0D69
|
||
Size X: 19
|
||
Size Y: 16
|
||
End: 224C
|
||
Length: 88
|
||
|
||
XXXXXXXXXXXX
|
||
XX.. X X
|
||
XX..& * * X
|
||
XX..&.X X X* XX
|
||
X..&.X X X * X
|
||
XXXX...X X X X
|
||
X XX X X
|
||
X @* * XXX X X XX
|
||
X * * X X X
|
||
XXX** X X X X X
|
||
X * X X XXXXX
|
||
X *X XXXXX X
|
||
X* X X X X
|
||
X XXX XX X
|
||
X X X XX
|
||
XXXX XXXXXX
|
||
|
||
*************************************
|
||
Maze: 50
|
||
File offset: 224D, DS:0EBD, table offset: 0DC1
|
||
Size X: 21
|
||
Size Y: 16
|
||
End: 22A7
|
||
Length: 91
|
||
|
||
XXXXXXXXXXXXX
|
||
X XXX X
|
||
X * * XXXX
|
||
XXXX X * * X
|
||
XX * X*XXXX * * X
|
||
XXX X X XXX * X
|
||
X * * X * X XXXX
|
||
X XX*XXXX X*X * XXX
|
||
X XX XXX X X X * X
|
||
X @* * X * X X
|
||
XXXXX X XX X *X X
|
||
X... XXXXX* X X X
|
||
X.......X ** X* X X
|
||
X.......X X
|
||
X.......XXXXXXX XX
|
||
XXXXXXXXX XXXX
|
||
|
||
*************************************
|
||
Maze: 51
|
||
File offset: 22A8, DS:0F18, table offset: 0E1C
|
||
Size X: 16
|
||
Size Y: 14
|
||
End: 22EA
|
||
Length: 67
|
||
|
||
XXXXX XXXX
|
||
X...X X XXXX
|
||
X...XXX * X
|
||
X....XX * *XXX
|
||
XX....XX * X
|
||
XXX... XX * * X
|
||
X XX X * X
|
||
X XX X XXX XXXX
|
||
X * X X* * X
|
||
X * @ * * X
|
||
X X * ** * XXX
|
||
X XXXXXX XXX
|
||
X XX XXXX
|
||
XXX
|
||
|
||
*************************************
|
||
Maze: 52
|
||
File offset: 22EB, DS:0F5B, table offset: 0E5F
|
||
Size X: 21
|
||
Size Y: 14
|
||
End: 2332
|
||
Length: 72
|
||
|
||
XXXX
|
||
XX XXXXX
|
||
X X XXXXX
|
||
X *XXX XXX X
|
||
X..X *X X X X
|
||
X..X **X XXX
|
||
X.&X X X* * XXXXX
|
||
X..X XX XX*X X
|
||
X.&* * X XX * X
|
||
X..XX * X XXXXXX
|
||
X.&XX*XX XXXXX
|
||
X.. * XXXXX
|
||
X X @ X
|
||
XXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 53
|
||
File offset: 2333, DS:0FA3, table offset: 0EA7
|
||
Size X: 13
|
||
Size Y: 19
|
||
End: 2382
|
||
Length: 80
|
||
|
||
XXXXXXXXXX
|
||
X XXX X
|
||
X * * X
|
||
X XXXX*XX
|
||
XX X X X
|
||
XX X.& X
|
||
X XX..X X
|
||
X @ X.&X XX
|
||
X X*X..X* X
|
||
X * X..X X
|
||
X X X&&X X
|
||
X * X..X*XX
|
||
X .&X X
|
||
XXX X X X
|
||
XX XXXX X
|
||
X XXXXXXX*XX
|
||
X * * X
|
||
X XX X X
|
||
XXXXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 54
|
||
File offset: 2383, DS:0FF3, table offset: 0EF7
|
||
Size X: 23
|
||
Size Y: 20
|
||
End: 23F8
|
||
Length: 118
|
||
|
||
XXXXXXXXXXXXXXXXXXXXX
|
||
X XX X X X X
|
||
X * * * * XX
|
||
XXXXX X X XXX XX*XXX
|
||
X X XX*XXXXXX X X
|
||
X * X ......X X * X
|
||
XX X X ......XXXXX X
|
||
XX XXXXXXXXX..X X XXX
|
||
X X..X * X
|
||
X XX XXX XXX..XX X XXX
|
||
X X X XX..XX XXX X
|
||
X @ *..X X
|
||
X X X XX X XX X
|
||
XXXXX XXXXXXXXXXXXXX XX
|
||
X X X * X
|
||
X * X * * * X X X
|
||
X X*XX *X XX XX X X
|
||
X * ** XXXX * * X X X
|
||
X X X X
|
||
XXXXXXXXXXXXXXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 55
|
||
File offset: 23F9, DS:1069, table offset: 0F6D
|
||
Size X: 22
|
||
Size Y: 15
|
||
End: 2450
|
||
Length: 88
|
||
|
||
XXXXXXXXXXXXXXXXXXXXX
|
||
XX X
|
||
X * X XX X X
|
||
X XXXXXX XXX X*XX XX
|
||
XX*X XX*X.... X X
|
||
X X * X....XX X X
|
||
X * X X X X....XX X
|
||
X * X** X....XX*X X
|
||
X X *@*XX*X....XX X
|
||
X *** X....X X
|
||
X *X X XXXXXX *XXX
|
||
XX X XXX** * * X
|
||
XX X * * XX X
|
||
XXXXX X XXXXXXX
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 56
|
||
File offset: 2451, DS:10C1, table offset: 0FC5
|
||
Size X: 14
|
||
Size Y: 16
|
||
End: 248B
|
||
Length: 59
|
||
|
||
XXXXXXXXXX
|
||
X XXXX
|
||
X XXXXXX X XX
|
||
X X * * * * X
|
||
X X* X
|
||
XXX* **X XXX
|
||
X XX X *XX
|
||
XX*X * @X
|
||
X * * XXX
|
||
X X * X
|
||
X XX X X
|
||
XX XXXXX X
|
||
X X
|
||
X.......XXX
|
||
X.......X
|
||
XXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 57
|
||
File offset: 248C, DS:10FC, table offset: 1000
|
||
Size X: 18
|
||
Size Y: 11
|
||
End: 24BD
|
||
Length: 50
|
||
|
||
XXXX
|
||
XXXXXXXXX XX
|
||
XX * * XXXXX
|
||
X XX XX XX...X
|
||
X X** * **X*XX...X
|
||
X X @ X ...X
|
||
X *X XXX** ...X
|
||
X * ** * XX....X
|
||
XXX* XXXXXXX
|
||
X XXXXXXX
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 58
|
||
File offset: 24BE, DS:112E, table offset: 1032
|
||
Size X: 27
|
||
Size Y: 20
|
||
End: 2542
|
||
Length: 133
|
||
|
||
XXXXXX
|
||
XXXXX X
|
||
X XX X XXXXX
|
||
X &.X..X X
|
||
XXXXX XXXX *X.X... X
|
||
X XXX XX X&....XX XX
|
||
X * XX X..X..XX X
|
||
XXXXXX X X X&.XXXXX X
|
||
X X *X*X X X..XXXXX X
|
||
X * * X X&. X X
|
||
XX XX * XXX X XX X X
|
||
X * * XXX XXXXX XX X
|
||
XXX*XXX*XXX XXXX XX X
|
||
XXXX X XXX X X
|
||
X * X *XXXX XXX**X@XXXXX
|
||
X * X X XXXX X*X X
|
||
XXXX X *X X X
|
||
X * X XX XX XXXXXXXX
|
||
XX XXX XXXXXXXX
|
||
XXXX
|
||
|
||
*************************************
|
||
Maze: 59
|
||
File offset: 2543, DS:11B3, table offset: 10B7
|
||
Size X: 29
|
||
Size Y: 20
|
||
End: 25DA
|
||
Length: 152
|
||
|
||
XXXX
|
||
X X
|
||
X XXXXXXXX
|
||
XXXXXXX X X
|
||
X X X X X X XX
|
||
X * * XX * X
|
||
XXX *X X X X XXXXXXXXX
|
||
X * X *X X ** X X X X
|
||
XX X X XXX * X X X
|
||
X X* X XXX X X **X X X
|
||
X *XX * X XX * X X XX
|
||
XXXX* * X XX X * ..X
|
||
X X XXX X * * XXX XXX.&X
|
||
X XX ** @ * XX....X
|
||
X XX XX * X*X XX....&.X
|
||
XX X * X X *XX XX....&.XXX
|
||
XX XX * X * X X....&.XXX
|
||
X * XXXX X ....&.XXX
|
||
X X X X X ..&.XXX
|
||
XXXXXXXX XXXXXXXXXXX
|
||
|
||
*************************************
|
||
Maze: 60
|
||
File offset: 25DB, DS:124B, table offset: 114F
|
||
Size X: 26
|
||
Size Y: 16
|
||
End: 2646
|
||
Length: 108
|
||
|
||
XXXXX
|
||
X XXXX
|
||
X * XXXX XXXX
|
||
X X *X XXXX X
|
||
XXXXXXXXXXX X * X X
|
||
X.. X * XXXX X X X
|
||
X..* X * X * X * .XX
|
||
X.&X X * * XX XX X.X
|
||
X..X* @ X XX ** X.X
|
||
X..X * * * * XX XX .X
|
||
X.&** X XX * X*X * X.X
|
||
X..X XX X X.X
|
||
X..XXXXXXX XXX XXXXXX.XX
|
||
X ** &.XX
|
||
X XXXXXXXXXXXXXXXXXX ..X
|
||
XXXX XXXXXX
|
||
|
||
*************************************
|
||
</script>
|
||
|
||
<script src="levels.js"></script>
|
||
<script src="solver.js"></script>
|
||
<script>
|
||
// ============ КОНСТАНТЫ ============
|
||
const canvas = document.getElementById('game');
|
||
const ctx = canvas.getContext('2d');
|
||
const TILE = 32;
|
||
const WALL = 1;
|
||
const TARGET = 2;
|
||
const MOVE_SPEED = 0.15;
|
||
|
||
let currentLevel = 0;
|
||
let COLS, ROWS, level, player, boxes;
|
||
let playerTargetX, playerTargetY, playerMoving = false, moveProgress = 0;
|
||
let movesCount = 0;
|
||
let levelComplete = false; // игрок дошёл до открытого выхода — уровень завершён
|
||
let allBoxesPlaced = false; // все ящики на месте — выход открыт, но ещё не пройден
|
||
let entranceX, entranceY, entranceOpen; // дверь входа — гаснет, как только игрок с неё сошёл
|
||
let exitX, exitY; // дверь выхода — появляется, когда все ящики на местах
|
||
|
||
let autoSolveActive = false;
|
||
let autoPlayMoves = null, autoPlayIndex = 0; // плоский список [dx,dy] — либо из солвера (развёрнутый), либо из сохранённого решения
|
||
|
||
let moveLog = []; // все реально сделанные ходы этой попытки уровня — пишем в топ решений при победе
|
||
let usedAutoSolve = false; // хоть раз в этой попытке играл автосолвер/G — решение не "ручное"
|
||
let pendingSolution = null; // солвер нашёл ходы, ждём клика "Решение найдено!" чтобы проиграть
|
||
|
||
// ============ ТОП РЕШЕНИЙ (localStorage, JSON) ============
|
||
// Ключ — имя уровня, значение — список решений, отсортированный по числу
|
||
// ходов (меньше — лучше). Одинаковые (по точной последовательности ходов)
|
||
// не дублируем. Пишутся и находки солвера, и ручные победы игрока.
|
||
const SOLUTIONS_KEY = 'sokoban-solutions-v1';
|
||
function loadSolutions() {
|
||
try { return JSON.parse(localStorage.getItem(SOLUTIONS_KEY)) || {}; } catch { return {}; }
|
||
}
|
||
function saveSolutions(all) {
|
||
try { localStorage.setItem(SOLUTIONS_KEY, JSON.stringify(all)); } catch {}
|
||
}
|
||
function recordSolution(levelName, moves, source) {
|
||
if (!moves.length) return;
|
||
const all = loadSolutions();
|
||
const list = all[levelName] || [];
|
||
const key = moves.map(m => m.join('')).join('|');
|
||
if (list.some(s => s.key === key)) return; // та же последовательность уже есть
|
||
list.push({ key, moves, count: moves.length, source, date: Date.now() });
|
||
list.sort((a, b) => a.count - b.count);
|
||
all[levelName] = list;
|
||
saveSolutions(all);
|
||
}
|
||
function bestSolution(levelName) {
|
||
const list = loadSolutions()[levelName];
|
||
return list && list.length ? list[0] : null;
|
||
}
|
||
|
||
// Разворачивает ходы солвера (только толчки + откуда толкать) в плоский
|
||
// список отдельных шагов игрока (ходьба + толчки), как если бы игрок жал
|
||
// стрелки сам — так и проигрывание, и запись в топ используют один формат.
|
||
function expandSolverMoves(pushMoves, fromX, fromY, levelGrid, startBoxes) {
|
||
const flat = [];
|
||
let px = fromX, py = fromY;
|
||
const simBoxes = startBoxes.map(b => ({ x: b.x, y: b.y }));
|
||
for (const move of pushMoves) {
|
||
const [dx, dy] = move.dir;
|
||
const bx = move.boxFrom.x, by = move.boxFrom.y;
|
||
const standX = bx - dx, standY = by - dy; // клетка ЗА ящиком, откуда толкать
|
||
while (px !== standX || py !== standY) {
|
||
const boxesForPath = simBoxes.map(b => ({ targetX: b.x, targetY: b.y }));
|
||
const path = findPath(px, py, standX, standY, levelGrid, boxesForPath);
|
||
if (!path || !path.length) return null; // раскладка успела измениться — не сходится
|
||
const [sx, sy] = path[0];
|
||
flat.push([sx, sy]);
|
||
px += sx; py += sy;
|
||
}
|
||
flat.push([dx, dy]);
|
||
const idx = simBoxes.findIndex(b => b.x === bx && b.y === by);
|
||
if (idx === -1) return null;
|
||
simBoxes[idx] = { x: bx + dx, y: by + dy };
|
||
px = bx; py = by; // после толчка игрок оказывается там, где был ящик
|
||
}
|
||
return flat;
|
||
}
|
||
|
||
function startFlatPlayback(moves) {
|
||
autoPlayMoves = moves;
|
||
autoPlayIndex = 0;
|
||
autoSolveActive = true;
|
||
usedAutoSolve = true;
|
||
updateHUD();
|
||
}
|
||
|
||
function autoPlayStep() {
|
||
if (playerMoving || boxes.some(b => b.moving)) return;
|
||
if (levelComplete || !autoPlayMoves || autoPlayIndex >= autoPlayMoves.length) {
|
||
autoPlayMoves = null;
|
||
autoSolveActive = false;
|
||
updateHUD();
|
||
return;
|
||
}
|
||
const [dx, dy] = autoPlayMoves[autoPlayIndex++];
|
||
tryMove(dx, dy);
|
||
}
|
||
|
||
function playBestSolution() {
|
||
if (autoSolveActive || levelComplete || solveState || playerMoving || boxes.some(b => b.moving)) return;
|
||
const best = bestSolution(LEVELS[currentLevel].name);
|
||
if (!best) { alert('Нет сохранённых решений для этого уровня — сначала пройди его (F или руками)'); return; }
|
||
startFlatPlayback(best.moves);
|
||
}
|
||
|
||
// Клетка под выход: не стена, не цель, не вход, обязательно достижима от
|
||
// старта. Считается ОДИН раз на уровень (см. вызов ниже, сразу после
|
||
// загрузки LEVELS) и кладётся прямо в данные уровня — не пересчитывается
|
||
// при каждом loadLevel (рестарт/переход/G), иначе место прыгало бы и
|
||
// записанные решения переставали сходиться с картой.
|
||
function pickExitCell(levelGrid, cols, rows, startX, startY) {
|
||
const visited = new Set([`${startX},${startY}`]);
|
||
const queue = [[startX, startY]];
|
||
const candidates = [];
|
||
while (queue.length) {
|
||
const [x, y] = queue.shift();
|
||
if (!(x === startX && y === startY) && levelGrid[y][x] !== TARGET) candidates.push([x, y]);
|
||
for (const [dx, dy] of [[0,-1],[0,1],[-1,0],[1,0]]) {
|
||
const nx = x+dx, ny = y+dy;
|
||
const key = `${nx},${ny}`;
|
||
if (nx>=0 && nx<cols && ny>=0 && ny<rows && levelGrid[ny][nx] !== WALL && !visited.has(key)) {
|
||
visited.add(key);
|
||
queue.push([nx, ny]);
|
||
}
|
||
}
|
||
}
|
||
if (!candidates.length) return [startX, startY]; // ponytail: вырожденный уровень без свободного пола — сажаем на вход, не должно случаться на реальных 60 уровнях
|
||
candidates.sort((a, b) => a[0]-b[0] || a[1]-b[1]);
|
||
return candidates[Math.floor(candidates.length / 2)]; // клетка из середины разброса, не первая попавшаяся у самого входа
|
||
}
|
||
|
||
// Считаем выход для каждого уровня один раз, сразу как LEVELS готов.
|
||
LEVELS.forEach(lvl => { lvl.exit = pickExitCell(lvl.map, lvl.cols, lvl.rows, lvl.player[0], lvl.player[1]); });
|
||
|
||
// ============ ЗАГРУЗКА ============
|
||
function loadLevel(index) {
|
||
const lvl = LEVELS[index];
|
||
COLS = lvl.cols;
|
||
ROWS = lvl.rows;
|
||
level = lvl.map.map(row => [...row]);
|
||
player = { x: lvl.player[0], y: lvl.player[1] };
|
||
playerTargetX = player.x;
|
||
playerTargetY = player.y;
|
||
playerMoving = false;
|
||
moveProgress = 0;
|
||
|
||
boxes = lvl.boxes.map(b => ({
|
||
x: b[0], y: b[1],
|
||
targetX: b[0], targetY: b[1],
|
||
moving: false, progress: 0
|
||
}));
|
||
|
||
movesCount = 0;
|
||
levelComplete = false;
|
||
allBoxesPlaced = false;
|
||
entranceX = player.x; entranceY = player.y; entranceOpen = true;
|
||
[exitX, exitY] = lvl.exit;
|
||
autoSolveActive = false;
|
||
autoPlayMoves = null;
|
||
moveLog = [];
|
||
usedAutoSolve = false;
|
||
pendingSolution = null;
|
||
resetSolveButton();
|
||
|
||
canvas.width = COLS * TILE;
|
||
canvas.height = ROWS * TILE;
|
||
|
||
updateHUD();
|
||
}
|
||
|
||
// ============ ЛОГИКА ============
|
||
function isWall(x, y) {
|
||
if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return true;
|
||
return level[y][x] === WALL;
|
||
}
|
||
|
||
function getBoxAt(x, y) {
|
||
return boxes.find(b => b.targetX === x && b.targetY === y && !b.moving);
|
||
}
|
||
|
||
function tryMove(dx, dy) {
|
||
if (playerMoving || levelComplete) return;
|
||
if (boxes.some(b => b.moving)) return;
|
||
|
||
const nx = player.x + dx, ny = player.y + dy;
|
||
if (isWall(nx, ny)) return;
|
||
|
||
const box = getBoxAt(nx, ny);
|
||
if (box) {
|
||
const bx = nx + dx, by = ny + dy;
|
||
if (isWall(bx, by) || getBoxAt(bx, by)) return;
|
||
box.targetX = bx; box.targetY = by;
|
||
box.moving = true; box.progress = 0;
|
||
}
|
||
|
||
playerTargetX = nx; playerTargetY = ny;
|
||
playerMoving = true; moveProgress = 0;
|
||
movesCount++;
|
||
moveLog.push([dx, dy]);
|
||
updateHUD();
|
||
}
|
||
|
||
function checkWin() {
|
||
return boxes.every(b => {
|
||
const bx = b.moving ? b.targetX : b.x;
|
||
const by = b.moving ? b.targetY : b.y;
|
||
return level[by]?.[bx] === TARGET;
|
||
});
|
||
}
|
||
|
||
function updateHUD() {
|
||
const onTarget = boxes.filter(b => {
|
||
const bx = b.moving ? b.targetX : b.x;
|
||
const by = b.moving ? b.targetY : b.y;
|
||
return level[by]?.[bx] === TARGET;
|
||
}).length;
|
||
document.getElementById('level-name').textContent =
|
||
`${currentLevel+1}/${LEVELS.length} ${LEVELS[currentLevel].name}${autoSolveActive ? ' 🤖' : ''}${allBoxesPlaced && !levelComplete ? ' 🚪 выход открыт' : ''}`;
|
||
document.getElementById('box-count').textContent = `📦 ${onTarget}/${boxes.length}`;
|
||
document.getElementById('moves-count').textContent = `👣 ${movesCount}`;
|
||
}
|
||
|
||
// ============ АВТОСОЛВЕР ============
|
||
// Поиск гоняется кусками через setTimeout(…,0), а не одним блокирующим
|
||
// вызовом — так браузер успевает перерисовать таймер и обработать клик по
|
||
// "Отмена" между кусками. Web Worker сюда не годится: игра открывается как
|
||
// file://, а Chrome с file:// воркеры не грузит (CORS).
|
||
let solveState = null; // { solver, startTime, timerInterval, cancelled }
|
||
|
||
function startAutoSolve() {
|
||
console.log('F нажата, autoSolveActive:', autoSolveActive, 'levelComplete:', levelComplete);
|
||
if (autoSolveActive || levelComplete || solveState) {
|
||
console.log('Выход: уже активно, уровень пройден или уже ищем');
|
||
return;
|
||
}
|
||
if (playerMoving || boxes.some(b => b.moving)) {
|
||
console.log('Выход: ещё движется');
|
||
return;
|
||
}
|
||
|
||
console.log('🔍 Ищем решение для уровня', currentLevel);
|
||
const lvl = LEVELS[currentLevel];
|
||
// Солвер должен планировать от ТЕКУЩЕГО состояния ящиков, а не от
|
||
// исходного расклада уровня — иначе после ручных ходов решение не сойдётся
|
||
// с реальной доской.
|
||
const liveLvl = { ...lvl, boxes: boxes.map(b => [b.x, b.y]) };
|
||
const solver = createSolver(liveLvl, player.x, player.y);
|
||
const startTime = Date.now();
|
||
const timerEl = document.getElementById('solve-timer');
|
||
timerEl.textContent = '0.0с';
|
||
document.getElementById('solve-overlay').classList.remove('hidden');
|
||
const timerInterval = setInterval(() => {
|
||
timerEl.textContent = ((Date.now()-startTime)/1000).toFixed(1) + 'с';
|
||
}, 100);
|
||
solveState = { solver, startTime, timerInterval, cancelled: false };
|
||
setTimeout(runSolveChunk, 0);
|
||
}
|
||
|
||
function runSolveChunk() {
|
||
if (!solveState || solveState.cancelled) return;
|
||
const result = solveState.solver.step(50); // 50мс работаем, потом отдаём управление браузеру
|
||
if (!result.done) {
|
||
setTimeout(runSolveChunk, 0);
|
||
return;
|
||
}
|
||
finishAutoSolve(result);
|
||
}
|
||
|
||
function finishAutoSolve(result) {
|
||
clearInterval(solveState.timerInterval);
|
||
solveState = null;
|
||
|
||
if (!result.solved) {
|
||
document.getElementById('solve-overlay').classList.add('hidden');
|
||
alert(`Решение не найдено (${result.explored} состояний)`);
|
||
return;
|
||
}
|
||
|
||
console.log(`✅ Найдено: ${result.moves.length} толчков, состояний: ${result.explored}`);
|
||
// не проигрываем сразу — ждём клика по кнопке "Решение найдено!"
|
||
pendingSolution = result;
|
||
const btn = document.getElementById('solve-cancel');
|
||
btn.textContent = 'Решение найдено!';
|
||
btn.classList.add('solve-found');
|
||
document.getElementById('solve-timer').textContent = `${result.moves.length} толчков`;
|
||
}
|
||
|
||
function resetSolveButton() {
|
||
const btn = document.getElementById('solve-cancel');
|
||
btn.textContent = 'Отмена';
|
||
btn.classList.remove('solve-found');
|
||
}
|
||
|
||
function cancelAutoSolve() {
|
||
pendingSolution = null;
|
||
resetSolveButton();
|
||
if (!solveState) { document.getElementById('solve-overlay').classList.add('hidden'); return; }
|
||
solveState.cancelled = true;
|
||
clearInterval(solveState.timerInterval);
|
||
document.getElementById('solve-overlay').classList.add('hidden');
|
||
solveState = null;
|
||
}
|
||
|
||
function onSolveButtonClick() {
|
||
if (!pendingSolution) { cancelAutoSolve(); return; }
|
||
const solved = pendingSolution;
|
||
pendingSolution = null;
|
||
resetSolveButton();
|
||
document.getElementById('solve-overlay').classList.add('hidden');
|
||
const flat = expandSolverMoves(solved.moves, player.x, player.y, level, boxes);
|
||
if (!flat) { alert('Раскладка успела измениться, решение больше не подходит'); return; }
|
||
startFlatPlayback(flat);
|
||
}
|
||
document.getElementById('solve-cancel').addEventListener('click', onSolveButtonClick);
|
||
|
||
// Дошёл ли игрок до открытого выхода — если да, запускаем переход.
|
||
function checkExitStep() {
|
||
if (!levelComplete && allBoxesPlaced && player.x === exitX && player.y === exitY) {
|
||
levelComplete = true;
|
||
autoSolveActive = false;
|
||
recordSolution(LEVELS[currentLevel].name, moveLog, usedAutoSolve ? 'solver' : 'player');
|
||
updateHUD();
|
||
setTimeout(goToNextLevelOrFinish, 300);
|
||
}
|
||
}
|
||
|
||
function goToNextLevelOrFinish() {
|
||
if (currentLevel + 1 < LEVELS.length) {
|
||
currentLevel++;
|
||
loadLevel(currentLevel);
|
||
} else {
|
||
alert('🚀 Станция полностью запитана. Игра пройдена!');
|
||
loadLevel(currentLevel); // даём побродить по последнему уровню ещё раз
|
||
}
|
||
}
|
||
|
||
// ============ ОБНОВЛЕНИЕ ============
|
||
function update() {
|
||
if (autoSolveActive) autoPlayStep();
|
||
|
||
if (playerMoving) {
|
||
moveProgress += MOVE_SPEED;
|
||
if (moveProgress >= 1) {
|
||
player.x = playerTargetX; player.y = playerTargetY;
|
||
playerMoving = false; moveProgress = 0;
|
||
if (entranceOpen && (player.x !== entranceX || player.y !== entranceY)) entranceOpen = false;
|
||
allBoxesPlaced = checkWin();
|
||
checkExitStep();
|
||
}
|
||
}
|
||
|
||
boxes.forEach(box => {
|
||
if (box.moving) {
|
||
box.progress += MOVE_SPEED;
|
||
if (box.progress >= 1) {
|
||
box.x = box.targetX; box.y = box.targetY;
|
||
box.moving = false; box.progress = 0;
|
||
allBoxesPlaced = checkWin();
|
||
checkExitStep();
|
||
updateHUD();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Дверь (вход/выход) — пульсирующее кольцо поверх клетки пола.
|
||
function drawDoor(px, py, color) {
|
||
const pulse = 0.5 + 0.5 * Math.sin(Date.now() / 250);
|
||
ctx.save();
|
||
ctx.shadowColor = color;
|
||
ctx.shadowBlur = 8 + pulse * 6;
|
||
ctx.strokeStyle = color;
|
||
ctx.lineWidth = 3;
|
||
ctx.beginPath();
|
||
ctx.arc(px+TILE/2, py+TILE/2, TILE/2.6, 0, Math.PI*2);
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
}
|
||
|
||
// ============ ОТРИСОВКА ============
|
||
function draw() {
|
||
ctx.fillStyle = '#0a0a15';
|
||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||
|
||
for (let y = 0; y < ROWS; y++) {
|
||
for (let x = 0; x < COLS; x++) {
|
||
const px = x * TILE, py = y * TILE;
|
||
const tile = level[y][x];
|
||
|
||
if (tile === WALL) {
|
||
if (allBoxesPlaced) {
|
||
// станция запитана — стены светятся
|
||
ctx.save();
|
||
ctx.shadowColor = '#7CFF6B';
|
||
ctx.shadowBlur = 6;
|
||
ctx.fillStyle = '#5d7a5a';
|
||
ctx.fillRect(px, py, TILE, TILE);
|
||
ctx.restore();
|
||
} else {
|
||
ctx.fillStyle = '#4a4e69';
|
||
ctx.fillRect(px, py, TILE, TILE);
|
||
}
|
||
} else {
|
||
ctx.fillStyle = (x+y)%2 ? '#252540' : '#2d2d44';
|
||
ctx.fillRect(px, py, TILE, TILE);
|
||
if (tile === TARGET) {
|
||
ctx.fillStyle = 'rgba(233,69,96,0.5)';
|
||
ctx.beginPath();
|
||
ctx.arc(px+TILE/2, py+TILE/2, TILE/5, 0, Math.PI*2);
|
||
ctx.fill();
|
||
}
|
||
if (entranceOpen && x === entranceX && y === entranceY) drawDoor(px, py, '#48cae4');
|
||
if (allBoxesPlaced && x === exitX && y === exitY) drawDoor(px, py, '#7CFF6B');
|
||
}
|
||
}
|
||
}
|
||
|
||
boxes.forEach(box => {
|
||
const bx = box.x + (box.targetX-box.x)*box.progress;
|
||
const by = box.y + (box.targetY-box.y)*box.progress;
|
||
const onT = level[Math.round(box.targetY)]?.[Math.round(box.targetX)] === TARGET;
|
||
const px = bx*TILE, py = by*TILE;
|
||
|
||
ctx.fillStyle = onT ? '#e94560' : '#d4a373';
|
||
ctx.fillRect(px+3, py+3, TILE-6, TILE-6);
|
||
ctx.strokeStyle = onT ? '#ff6b8a' : '#f0c27a';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(px+3, py+3, TILE-6, TILE-6);
|
||
});
|
||
|
||
const ppx = (player.x+(playerTargetX-player.x)*moveProgress)*TILE;
|
||
const ppy = (player.y+(playerTargetY-player.y)*moveProgress)*TILE;
|
||
ctx.fillStyle = '#48cae4';
|
||
ctx.beginPath();
|
||
ctx.arc(ppx+TILE/2, ppy+TILE/2, TILE/3, 0, Math.PI*2);
|
||
ctx.fill();
|
||
}
|
||
|
||
// ============ ВВОД ============
|
||
const keys = {};
|
||
window.addEventListener('keydown', e => {
|
||
const key = e.key.toLowerCase();
|
||
|
||
if (key === 'escape' && (solveState || pendingSolution)) { cancelAutoSolve(); e.preventDefault(); return; }
|
||
if (solveState || pendingSolution) { e.preventDefault(); return; } // поиск идёт или ждём клика по найденному — левел не трогаем
|
||
|
||
// Системные клавиши — обрабатываем сразу
|
||
if (key === 'r') { loadLevel(currentLevel); e.preventDefault(); return; }
|
||
if (key === 'n') { currentLevel=(currentLevel+1)%LEVELS.length; loadLevel(currentLevel); e.preventDefault(); return; }
|
||
if (key === 'p') { currentLevel=(currentLevel-1+LEVELS.length)%LEVELS.length; loadLevel(currentLevel); e.preventDefault(); return; }
|
||
if (key === 'f') { startAutoSolve(); e.preventDefault(); return; }
|
||
if (key === 'g') { playBestSolution(); e.preventDefault(); return; }
|
||
|
||
// Стрелки и WASD
|
||
if (['arrowup','arrowdown','arrowleft','arrowright','w','a','s','d'].includes(key)) {
|
||
keys[key] = true;
|
||
e.preventDefault();
|
||
}
|
||
});
|
||
|
||
window.addEventListener('keyup', e => {
|
||
keys[e.key.toLowerCase()] = false;
|
||
});
|
||
|
||
function processInput() {
|
||
if (autoSolveActive) return;
|
||
if (playerMoving||boxes.some(b=>b.moving)||levelComplete) return;
|
||
|
||
if (keys['arrowup']||keys['w']) tryMove(0,-1);
|
||
else if (keys['arrowdown']||keys['s']) tryMove(0,1);
|
||
else if (keys['arrowleft']||keys['a']) tryMove(-1,0);
|
||
else if (keys['arrowright']||keys['d']) tryMove(1,0);
|
||
}
|
||
|
||
// ============ ЦИКЛ ============
|
||
function gameLoop() { processInput(); update(); draw(); requestAnimationFrame(gameLoop); }
|
||
|
||
// Ждём загрузки LEVELS
|
||
setTimeout(() => {
|
||
if (typeof LEVELS !== 'undefined' && LEVELS.length > 0) {
|
||
loadLevel(0);
|
||
gameLoop();
|
||
} else {
|
||
console.error('LEVELS не загружены!');
|
||
}
|
||
}, 100);
|
||
</script>
|
||
</body>
|
||
</html> |