Compare commits
41 commits
multiple-w
...
master
Author | SHA1 | Date | |
---|---|---|---|
65bdce0140 | |||
707bfb0059 | |||
ed5e64c6b7 | |||
097f1cbfb4 | |||
aae1d30eb9 | |||
ce0639fd40 | |||
3756979b6a | |||
f4229bfc48 | |||
0646e57826 | |||
7ef36d98f2 | |||
8233847f46 | |||
22b41082a7 | |||
6e718eb3b0 | |||
30ada92db7 | |||
d224ba7805 | |||
0d9afa4ced | |||
bd5b9cc4cb | |||
2e53f92179 | |||
32778cf9fd | |||
eb53739618 | |||
7fcb93c83c | |||
55d0bff81c | |||
cf4842e124 | |||
d6cf4c1505 | |||
603dcf502c | |||
3f872c169c | |||
75c3ef6f4b | |||
ca2496f7d5 | |||
5a12540259 | |||
5ed93c28da | |||
0c42d8452c | |||
1c1307a0e2 | |||
0a59ab8b21 | |||
fda82e0cd7 | |||
1370bfd9a1 | |||
80ab0405ec | |||
1cd3618b6f | |||
123d7508d1 | |||
a5b089520b | |||
6c1c4fa0eb | |||
542682e126 |
25 changed files with 1880 additions and 4305 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -11,5 +11,6 @@ config.lua
|
|||
*.srm
|
||||
*.sfc
|
||||
*.bst
|
||||
watchexec*
|
||||
namedpipe*
|
||||
build/
|
||||
.VSCodeCounter/
|
||||
|
|
7
LICENSE.namedpipe.md
Normal file
7
LICENSE.namedpipe.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
Copyright © 2014 Peter S. May
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
3525
LibDeflate.lua
3525
LibDeflate.lua
File diff suppressed because it is too large
Load diff
51
README.md
51
README.md
|
@ -2,22 +2,45 @@
|
|||
|
||||
An AI based on SethBling's MarI/O to play Donkey Kong Country 2 with lsnes.
|
||||
|
||||
See [YouTube](https://www.youtube.com/watch?v=Q69_wmEkp-k) for an example run.
|
||||
See [YouTube](https://www.youtube.com/watch?v=-\_UyUbObLeE) for an example run.
|
||||
|
||||
## Requirements
|
||||
|
||||
* lsnes with **Lua 5.2** (do not try to build with 5.3, it does not work!)
|
||||
* inotifywait for Linux, or a fairly recent version of Windows that has PowerShell
|
||||
* socat for Linux, or a fairly recent version of Windows that has PowerShell
|
||||
* A Donkey Kong Country 2 1.1 US ROM (matching hash b79c2bb86f6fc76e1fc61c62fc16d51c664c381e58bc2933be643bbc4d8b610c)
|
||||
|
||||
### Windows
|
||||
|
||||
You will want to install the rrtest-1613424691 version of lsnes. Older versions were crashing for me. The easiest way to do this is to use Chocolatey:
|
||||
|
||||
```powershell
|
||||
# Make sure you use an Administrator shell!
|
||||
|
||||
# Skip this command if you have Chocolatey already.
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
|
||||
|
||||
# Install lsnes
|
||||
choco install --version 2.0.24-rrtest-1613424691 lsnes
|
||||
```
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Start lsnes
|
||||
2. Go to `Configure -> Settings -> Advanced` and change `LUA -> Maximum memory use` to `1024MB`
|
||||
3. Load the DKC2 ROM: `File -> Load -> ROM...`
|
||||
4. Load the `neat-donk.lua` script: `Tools -> Run Lua script...`
|
||||
5. You may also want to turn off sound since it may get annoying. `Configure -> Sounds enabled`
|
||||
6. Look at config.lua for some settings you can change. Not all have been tested, but you should be able to change the number on the `_M.Filename =` line to get a different state file from the `_M.State` list. Also note the `Threads =` line. Change this to 1 to prevent multiple instances of lsnes from getting launched at once. If you use more than 1 thread, you may also want to launch `lsnes` using xpra to manage the windows, with the `xpra-run.sh` script. Currently Windows does not support multiple threads.
|
||||
3. Load the `neat-donk.lua` script: `Tools -> Run Lua script...`
|
||||
|
||||
## Config
|
||||
|
||||
Look at config.lua for some settings you can change. Not all have been tested.
|
||||
|
||||
* `_M.ROM`: The ROM path, `rom.sfc` by default.
|
||||
* `_M.Filename`: Change the number to a different one from the `_M.State` list
|
||||
to load a different file.
|
||||
* `_M.NeatConfig.Threads`: Change this to 1 to prevent multiple instances of
|
||||
lsnes from getting launched at once, or increase it to run more instances.
|
||||
If you use more than 1 thread, you may also want to launch `lsnes` using xpra
|
||||
to manage the windows, with the [xpra-run.sh](xpra-run.sh) script.
|
||||
|
||||
## Keys
|
||||
1: Stop/start
|
||||
|
@ -34,17 +57,25 @@ See [YouTube](https://www.youtube.com/watch?v=Q69_wmEkp-k) for an example run.
|
|||
|
||||
### Status Overlay
|
||||
|
||||
The status overlay is located at [tools/status-overlay.lua](tools/status-overlay.lua). It will help you see the tile and sprite calculations by marking the tiles with their offsets on the screen, giving a crosshair with tile measurements (every 32 pixels), and listing information about the sprites (you can use the 1 and 2 keys above the letter keys to page through them). Sprites labeled in green are considered "good", red is "bad", normal color is neutral. Solid red means that it's the active sprite in the info viewer.
|
||||
The status overlay is located at [tools/status-overlay.lua](tools/status-overlay.lua).
|
||||
It will help you see the tile and sprite calculations by marking the tiles with
|
||||
their offsets on the screen, giving a crosshair with tile measurements every
|
||||
32 pixels, and listing information about the sprites. You can use the 1 and 2
|
||||
keys above the letter keys to page through them. Sprites labeled in green are
|
||||
considered "good", red is "bad", normal color is neutral. Solid red means that
|
||||
it's the active sprite in the info viewer.
|
||||
|
||||
<img src="https://github.com/empathicqubit/neat-donk/blob/master/doc/donkutil.png?raw=true" />
|
||||
|
||||
### BSNES Launcher
|
||||
|
||||
Located at [tools/bsnes-launcher.lua](tools/bsnes-launcher.lua), this script gives you an easy way to launch bsnes-plus with breakpoints preset. Run it in lsnes and it will display a message to the Lua console and stderr on how to use it.
|
||||
Located at [tools/bsnes-launcher.lua](tools/bsnes-launcher.lua), this script
|
||||
gives you an easy way to launch bsnes-plus with breakpoints preset. Run it in
|
||||
lsnes and it will display a message to the Lua console and stderr on how to use it.
|
||||
|
||||
## Notes
|
||||
* Only tested on Pirate Panic
|
||||
* The pool files are gzipped Serpent data
|
||||
* The pool files are PKZIP files with one file, data.serpent, containing Serpent-formatted data
|
||||
|
||||
## Credits
|
||||
|
||||
|
@ -53,8 +84,8 @@ Located at [tools/bsnes-launcher.lua](tools/bsnes-launcher.lua), this script giv
|
|||
* [Basic tilemap info from p4plus2/DKC2-disassembly](https://github.com/p4plus2/DKC2-disassembly)
|
||||
* [Serpent](https://github.com/pkulchenko/serpent)
|
||||
* [LibDeflate](https://github.com/SafeteeWoW/LibDeflate)
|
||||
* [watchexec](https://github.com/watchexec/watchexec/blob/main/LICENSE)
|
||||
* [Billiam's Promise library](https://github.com/Billiam/promise.lua)
|
||||
* [https://github.com/psmay/windows-named-pipe-utils](https://github.com/psmay/windows-named-pipe-utils)
|
||||
|
||||
## TODO
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ _M.State = {
|
|||
|
||||
-- W1.2 Mainbrace Mayhem
|
||||
"MainbraceMayhem.lsmv", -- [4]
|
||||
"MainbraceMayhemBonus.lsmv", -- [5]
|
||||
"MainbraceMayhemTopOfRope.lsmv", -- [6]
|
||||
}
|
||||
|
||||
_M.Filename = _M.PoolDir .. _M.State[4]
|
||||
|
@ -41,8 +43,10 @@ _M.StartPowerup = 0
|
|||
_M.NeatConfig = {
|
||||
DisableSound = true,
|
||||
Threads = 7,
|
||||
ShowInterface = false,
|
||||
AutoSave = true,
|
||||
--Filename = "DP1.state",
|
||||
SaveFile = _M.Filename .. ".pool",
|
||||
SaveFile = _M.PoolDir .. "bigbrain.pool",
|
||||
|
||||
Filename = _M.Filename,
|
||||
Population = 300,
|
||||
|
@ -59,7 +63,7 @@ BiasMutationChance = 0.40,
|
|||
StepSize = 0.1,
|
||||
DisableMutationChance = 0.4,
|
||||
EnableMutationChance = 0.2,
|
||||
TimeoutConstant = 20,
|
||||
TimeoutConstant = 30,
|
||||
MaxNodes = 1000000,
|
||||
}
|
||||
|
||||
|
|
255
game.lua
255
game.lua
|
@ -1,5 +1,5 @@
|
|||
--Notes here
|
||||
local memory, bit, memory2, input, callback, movie = memory, bit, memory2, input, callback, movie
|
||||
local memory, bit, memory2, input, callback, movie, utime = memory, bit, memory2, input, callback, movie, utime
|
||||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
|
@ -126,8 +126,7 @@ end
|
|||
|
||||
local function processRewind()
|
||||
for i=#onRewindQueue,1,-1 do
|
||||
local promise = table.remove(onRewindQueue, i)
|
||||
promise:resolve()
|
||||
table.remove(onRewindQueue, i):resolve()
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -197,11 +196,79 @@ function _M.findPreferredExit()
|
|||
end)
|
||||
end
|
||||
|
||||
function _M.getGoalHit()
|
||||
local sprites = _M.getSprites()
|
||||
--- Starting from a specified point in the area, weave back and forth until we reach
|
||||
--- fall to the bottom. Return all points where we hit the floor on the way down.
|
||||
---@param startX integer x coordinate of the starting point
|
||||
---@param startY integer y coordinate of the starting point
|
||||
---@return table table A list of all the points that we collided with a platform
|
||||
function _M.getWaypoints(startX, startY)
|
||||
local areaWidth = _M.getAreaWidth()
|
||||
local areaHeight = _M.getAreaHeight()
|
||||
local increment = mem.size.tile
|
||||
local direction = increment
|
||||
local terminus = areaWidth
|
||||
-- If we're on the right half, move left
|
||||
-- If we're on the left half, move right
|
||||
if startX > areaWidth / 2 then
|
||||
direction = -direction
|
||||
terminus = 0x100
|
||||
end
|
||||
|
||||
local collisions = {}
|
||||
local currentY = startY
|
||||
local currentX = startX
|
||||
local switches = 0
|
||||
while currentY < areaHeight - increment do
|
||||
switches = 0
|
||||
-- Drop down until we collide with the floor
|
||||
while currentY < areaHeight - increment and _M.getAbsoluteTile(currentX, currentY) ~= 1 do
|
||||
currentY = currentY + increment
|
||||
end
|
||||
-- Track the collision
|
||||
table.insert(collisions, {
|
||||
x = currentX,
|
||||
y = currentY,
|
||||
})
|
||||
-- Break if we've hit the bottom
|
||||
if currentY > areaHeight - increment then
|
||||
break
|
||||
end
|
||||
-- Move in the direction until we reach a gap or the edge of the area
|
||||
while currentY < areaHeight - increment do
|
||||
currentX = currentX + direction
|
||||
-- Switch directions if we're out of bounds
|
||||
if direction < 0 and currentX < terminus + increment or direction > 0 and currentX > terminus - increment then
|
||||
switches = switches + 1
|
||||
if switches > 2 then
|
||||
currentY = currentY + increment
|
||||
break
|
||||
end
|
||||
if terminus == 0x100 then
|
||||
terminus = areaWidth
|
||||
direction = increment
|
||||
else
|
||||
terminus = 0x100
|
||||
direction = -increment
|
||||
end
|
||||
elseif _M.getAbsoluteTile(currentX, currentY) ~= 1 then
|
||||
-- Check to make sure there isn't a floor immediately underneath.
|
||||
-- If there is we're probably on an incline.
|
||||
if _M.getAbsoluteTile(currentX, currentY + increment) == 1 then
|
||||
currentY = currentY + increment
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return collisions
|
||||
end
|
||||
|
||||
function _M.getGoalHit(sprites)
|
||||
for i=1,#sprites,1 do
|
||||
local sprite = sprites[i]
|
||||
if sprite.control ~= 0x0164 then
|
||||
if sprite.control ~= spritelist.GoodSprites.goalBarrel then
|
||||
goto continue
|
||||
end
|
||||
-- Check if the goal barrel is moving up
|
||||
|
@ -346,9 +413,9 @@ function _M.tileIsSolid(x, y, tileVal, tileOffset)
|
|||
return true
|
||||
end
|
||||
|
||||
function _M.getTile(dx, dy)
|
||||
local tileX = math.floor((_M.partyX + dx * mem.size.tile) / mem.size.tile) * mem.size.tile
|
||||
local tileY = math.floor((_M.partyY + dy * mem.size.tile) / mem.size.tile) * mem.size.tile
|
||||
function _M.getAbsoluteTile(x, y)
|
||||
local tileX = math.floor(x / mem.size.tile) * mem.size.tile
|
||||
local tileY = math.floor(y / mem.size.tile) * mem.size.tile
|
||||
|
||||
local offset = _M.tileOffsetCalculation(tileX, tileY, _M.vertical)
|
||||
|
||||
|
@ -361,6 +428,10 @@ function _M.getTile(dx, dy)
|
|||
return 1
|
||||
end
|
||||
|
||||
function _M.getTile(dx, dy)
|
||||
return _M.getAbsoluteTile(_M.partyX + dx * mem.size.tile, _M.partyY + dy * mem.size.tile)
|
||||
end
|
||||
|
||||
function _M.getCurrentArea()
|
||||
return memory.readword(mem.addr.currentAreaNumber)
|
||||
end
|
||||
|
@ -373,6 +444,24 @@ function _M.getJumpHeight()
|
|||
return sprite.jumpHeight
|
||||
end
|
||||
|
||||
function _M.diedFromHit()
|
||||
local sprite = _M.getSprite(_M.leader)
|
||||
if sprite == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
return sprite.motion == 0x05
|
||||
end
|
||||
|
||||
function _M.fell()
|
||||
local sprite = _M.getSprite(_M.leader)
|
||||
if sprite == nil then
|
||||
return 0
|
||||
end
|
||||
|
||||
return sprite.motion == 0x3b
|
||||
end
|
||||
|
||||
function _M.getSprite(idx)
|
||||
local baseAddr = idx * mem.size.sprite + mem.addr.spriteBase
|
||||
local spriteData = memory.readregion(baseAddr, mem.size.sprite)
|
||||
|
@ -395,8 +484,9 @@ function _M.getSprite(idx)
|
|||
-- 0x4000 0: Right facing 1: Flipped
|
||||
-- 0x1000 0: Alive 1: Dying
|
||||
style = util.regionToWord(spriteData, offsets.style),
|
||||
velocityX = util.regionToWord(spriteData, offsets.velocityX),
|
||||
velocityY = util.regionToWord(spriteData, offsets.velocityY),
|
||||
velocityX = util.regionToSWord(spriteData, offsets.velocityX),
|
||||
velocityY = util.regionToSWord(spriteData, offsets.velocityY),
|
||||
motion = util.regionToWord(spriteData, offsets.motion),
|
||||
x = x,
|
||||
y = y,
|
||||
good = spritelist.Sprites[control]
|
||||
|
@ -426,9 +516,8 @@ end
|
|||
|
||||
-- Currently only for single bananas since they don't
|
||||
-- count as regular computed sprites
|
||||
function _M.getExtendedSprites()
|
||||
function _M.getExtendedSprites(sprites)
|
||||
local oam = memory2.OAM:readregion(0x00, 0x220)
|
||||
local sprites = _M.getSprites()
|
||||
local extended = {}
|
||||
|
||||
for idx=0,0x200/4-1,1 do
|
||||
|
@ -471,61 +560,90 @@ function _M.getExtendedSprites()
|
|||
end
|
||||
|
||||
function _M.getInputs()
|
||||
_M.getPositions()
|
||||
|
||||
local sprites = _M.getSprites()
|
||||
local extended = _M.getExtendedSprites()
|
||||
|
||||
local inputs = {}
|
||||
local inputDeltaDistance = {}
|
||||
|
||||
_M.getPositions()
|
||||
|
||||
local sprites = _M.getSprites()
|
||||
local extended = _M.getExtendedSprites(sprites)
|
||||
|
||||
local inputs = {}
|
||||
local inputDeltaDistance = {}
|
||||
|
||||
for dy = -config.BoxRadius, config.BoxRadius, 1 do
|
||||
for dx = -config.BoxRadius, config.BoxRadius, 1 do
|
||||
inputs[#inputs+1] = 0
|
||||
inputDeltaDistance[#inputDeltaDistance+1] = 1
|
||||
|
||||
local tile = _M.getTile(dx, dy)
|
||||
if tile == 1 then
|
||||
if _M.getTile(dx, dy-1) == 1 then
|
||||
inputs[#inputs+1] = 0
|
||||
inputDeltaDistance[#inputDeltaDistance+1] = 1
|
||||
|
||||
local tile = _M.getTile(dx, dy)
|
||||
if tile == 1 then
|
||||
if inputs[#inputs-config.BoxRadius*2-1] == -1 then
|
||||
inputs[#inputs] = -1
|
||||
else
|
||||
inputs[#inputs] = 1
|
||||
end
|
||||
elseif tile == 0 and _M.getTile(dx + 1, dy) == 1 and _M.getTile(dx + 1, dy - 1) == 1 then
|
||||
inputs[#inputs] = -1
|
||||
end
|
||||
|
||||
for i = 1,#sprites do
|
||||
local sprite = sprites[i]
|
||||
local distx = math.abs(sprite.x - (_M.partyX+dx*mem.size.tile))
|
||||
local disty = math.abs(sprite.y - (_M.partyY+dy*mem.size.tile))
|
||||
local dist = math.sqrt((distx * distx) + (disty * disty))
|
||||
if dist <= mem.size.tile * 1.25 then
|
||||
inputs[#inputs] = sprite.good
|
||||
|
||||
if dist > mem.size.tile then
|
||||
inputDeltaDistance[#inputDeltaDistance] = mathFunctions.squashDistance(dist)
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
local neighbors = 0
|
||||
for ddy=-1,1,1 do
|
||||
for ddx=-1,1,1 do
|
||||
if (ddy == 0 and ddx == 0) or (ddx == 0 and ddy == 1) then
|
||||
goto continue
|
||||
end
|
||||
|
||||
for i = 1,#extended do
|
||||
local distx = math.abs(extended[i]["x"]+_M.cameraX - (_M.partyX+dx*mem.size.tile))
|
||||
local disty = math.abs(extended[i]["y"]+_M.cameraY - (_M.partyY+dy*mem.size.tile))
|
||||
if distx < mem.size.tile / 2 and disty < mem.size.tile / 2 then
|
||||
|
||||
inputs[#inputs] = extended[i]["good"]
|
||||
local dist = math.sqrt((distx * distx) + (disty * disty))
|
||||
if dist > mem.size.tile / 2 then
|
||||
inputDeltaDistance[#inputDeltaDistance] = mathFunctions.squashDistance(dist)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return inputs, inputDeltaDistance
|
||||
if _M.getTile(dx+ddx, dy+ddy) == 0 then
|
||||
neighbors = neighbors + 1
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
if neighbors >= 3 then
|
||||
inputs[#inputs] = 1
|
||||
else
|
||||
inputs[#inputs] = -1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1,#sprites do
|
||||
local sprite = sprites[i]
|
||||
if sprite.good == 0 then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local distx = math.abs(sprite.x - (_M.partyX+dx*mem.size.tile))
|
||||
local disty = math.abs(sprite.y - (_M.partyY+dy*mem.size.tile))
|
||||
local dist = math.sqrt((distx * distx) + (disty * disty))
|
||||
if dist <= mem.size.tile * 1.25 then
|
||||
inputs[#inputs] = sprite.good
|
||||
|
||||
if dist > mem.size.tile then
|
||||
inputDeltaDistance[#inputDeltaDistance] = mathFunctions.squashDistance(dist)
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
|
||||
for i = 1,#extended do
|
||||
local distx = math.abs(extended[i]["x"]+_M.cameraX - (_M.partyX+dx*mem.size.tile))
|
||||
local disty = math.abs(extended[i]["y"]+_M.cameraY - (_M.partyY+dy*mem.size.tile))
|
||||
if distx < mem.size.tile / 2 and disty < mem.size.tile / 2 then
|
||||
|
||||
inputs[#inputs] = extended[i]["good"]
|
||||
local dist = math.sqrt((distx * distx) + (disty * disty))
|
||||
if dist > mem.size.tile / 2 then
|
||||
inputDeltaDistance[#inputDeltaDistance] = mathFunctions.squashDistance(dist)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return inputs, inputDeltaDistance
|
||||
end
|
||||
|
||||
function _M.getClimbing()
|
||||
local sprite = _M.getSprite(_M.leader)
|
||||
if sprite == nil then
|
||||
return false
|
||||
end
|
||||
return sprite.motion >= 0x35 and sprite.motion <= 0x39
|
||||
end
|
||||
|
||||
function _M.clearJoypad()
|
||||
|
@ -587,6 +705,17 @@ local function processMapLoad()
|
|||
areaLoadedQueue = {} -- We clear this because it doesn't make any sense after the map screen loads
|
||||
end
|
||||
|
||||
function _M.bonusScreenDisplayed(inputs)
|
||||
local count = 0
|
||||
for i=1,#inputs,1 do
|
||||
if inputs[i] ~= 0 then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
return count < 10
|
||||
end
|
||||
|
||||
local handlers = {}
|
||||
local function registerHandler(space, regname, addr, callback)
|
||||
table.insert(handlers, {
|
||||
|
|
458
i3.json
Normal file
458
i3.json
Normal file
|
@ -0,0 +1,458 @@
|
|||
[
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "splith",
|
||||
"percent": 1,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "splitv",
|
||||
"percent": 0.25,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "tabbed",
|
||||
"percent": 0.5,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 106,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": " - Terminal",
|
||||
"percent": 0.333333333333333,
|
||||
"swallows": [
|
||||
{
|
||||
"title": "[Tt]erminal"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 114,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes: Messages",
|
||||
"percent": 0.333333333333333,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Messages"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 122,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes rr2-β24 [null core]",
|
||||
"percent": 0.333333333333333,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "null "
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "tabbed",
|
||||
"percent": 0.5,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 1914,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes rr2-β24 [bsnes v085 (Compatibility core)]",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Compatibility"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 634,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes: Messages",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Messages"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "splitv",
|
||||
"percent": 0.25,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "tabbed",
|
||||
"percent": 0.5,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 954,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes rr2-β24 [bsnes v085 (Compatibility core)]",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Compatibility"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 474,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes: Messages",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Messages"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "tabbed",
|
||||
"percent": 0.5,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 378,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes rr2-β24 [bsnes v085 (Compatibility core)]",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Compatibility"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 234,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes: Messages",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Messages"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "splitv",
|
||||
"percent": 0.25,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "tabbed",
|
||||
"percent": 0.5,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 314,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes rr2-β24 [bsnes v085 (Compatibility core)]",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Compatibility"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 208,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes: Messages",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Messages"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "tabbed",
|
||||
"percent": 0.5,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 269,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes rr2-β24 [bsnes v085 (Compatibility core)]",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Compatibility"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 154,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes: Messages",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Messages"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "splitv",
|
||||
"percent": 0.25,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "tabbed",
|
||||
"percent": 0.5,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 186,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes rr2-β24 [bsnes v085 (Compatibility core)]",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Compatibility"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 132,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes: Messages",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Messages"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"floating": "auto_off",
|
||||
"layout": "tabbed",
|
||||
"percent": 0.5,
|
||||
"type": "con",
|
||||
"nodes": [
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 168,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes rr2-β24 [bsnes v085 (Compatibility core)]",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Compatibility"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
},
|
||||
{
|
||||
"border": "normal",
|
||||
"current_border_width": 3,
|
||||
"floating": "auto_off",
|
||||
"geometry": {
|
||||
"height": 1030,
|
||||
"width": 141,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"name": "lsnes: Messages",
|
||||
"percent": 0.5,
|
||||
"swallows": [
|
||||
{
|
||||
"class": "^Lsnes$",
|
||||
"title": "Messages"
|
||||
}
|
||||
],
|
||||
"type": "con"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
279
inflate-bit32.lua
Normal file
279
inflate-bit32.lua
Normal file
|
@ -0,0 +1,279 @@
|
|||
-- zzlib-bit32 - zlib decompression in Lua - version using bit/bit32 libraries
|
||||
|
||||
-- Copyright (c) 2016-2020 Francois Galea <fgalea at free.fr>
|
||||
-- This program is free software. It comes without any warranty, to
|
||||
-- the extent permitted by applicable law. You can redistribute it
|
||||
-- and/or modify it under the terms of the Do What The Fuck You Want
|
||||
-- To Public License, Version 2, as published by Sam Hocevar. See
|
||||
-- the COPYING file or http://www.wtfpl.net/ for more details.
|
||||
|
||||
|
||||
local inflate = {}
|
||||
|
||||
local bit = bit32 or bit
|
||||
|
||||
inflate.band = bit.band
|
||||
inflate.rshift = bit.rshift
|
||||
|
||||
function inflate.bitstream_init(file)
|
||||
local bs = {
|
||||
file = file, -- the open file handle
|
||||
buf = nil, -- character buffer
|
||||
len = nil, -- length of character buffer
|
||||
pos = 1, -- position in char buffer
|
||||
b = 0, -- bit buffer
|
||||
n = 0, -- number of bits in buffer
|
||||
}
|
||||
-- get rid of n first bits
|
||||
function bs:flushb(n)
|
||||
self.n = self.n - n
|
||||
self.b = bit.rshift(self.b,n)
|
||||
end
|
||||
-- peek a number of n bits from stream
|
||||
function bs:peekb(n)
|
||||
while self.n < n do
|
||||
if self.pos > self.len then
|
||||
self.buf = self.file:read(4096)
|
||||
self.len = self.buf:len()
|
||||
self.pos = 1
|
||||
end
|
||||
self.b = self.b + bit.lshift(self.buf:byte(self.pos),self.n)
|
||||
self.pos = self.pos + 1
|
||||
self.n = self.n + 8
|
||||
end
|
||||
return bit.band(self.b,bit.lshift(1,n)-1)
|
||||
end
|
||||
-- get a number of n bits from stream
|
||||
function bs:getb(n)
|
||||
local ret = bs:peekb(n)
|
||||
self.n = self.n - n
|
||||
self.b = bit.rshift(self.b,n)
|
||||
return ret
|
||||
end
|
||||
-- get next variable-size of maximum size=n element from stream, according to Huffman table
|
||||
function bs:getv(hufftable,n)
|
||||
local e = hufftable[bs:peekb(n)]
|
||||
local len = bit.band(e,15)
|
||||
local ret = bit.rshift(e,4)
|
||||
self.n = self.n - len
|
||||
self.b = bit.rshift(self.b,len)
|
||||
return ret
|
||||
end
|
||||
function bs:close()
|
||||
if self.file then
|
||||
self.file:close()
|
||||
end
|
||||
end
|
||||
if type(file) == "string" then
|
||||
bs.file = nil
|
||||
bs.buf = file
|
||||
else
|
||||
bs.buf = file:read(4096)
|
||||
end
|
||||
bs.len = bs.buf:len()
|
||||
return bs
|
||||
end
|
||||
|
||||
local function hufftable_create(depths)
|
||||
local nvalues = #depths
|
||||
local nbits = 1
|
||||
local bl_count = {}
|
||||
local next_code = {}
|
||||
for i=1,nvalues do
|
||||
local d = depths[i]
|
||||
if d > nbits then
|
||||
nbits = d
|
||||
end
|
||||
bl_count[d] = (bl_count[d] or 0) + 1
|
||||
end
|
||||
local table = {}
|
||||
local code = 0
|
||||
bl_count[0] = 0
|
||||
for i=1,nbits do
|
||||
code = (code + (bl_count[i-1] or 0)) * 2
|
||||
next_code[i] = code
|
||||
end
|
||||
for i=1,nvalues do
|
||||
local len = depths[i] or 0
|
||||
if len > 0 then
|
||||
local e = (i-1)*16 + len
|
||||
local code = next_code[len]
|
||||
local rcode = 0
|
||||
for j=1,len do
|
||||
rcode = rcode + bit.lshift(bit.band(1,bit.rshift(code,j-1)),len-j)
|
||||
end
|
||||
for j=0,2^nbits-1,2^len do
|
||||
table[j+rcode] = e
|
||||
end
|
||||
next_code[len] = next_code[len] + 1
|
||||
end
|
||||
end
|
||||
return table,nbits
|
||||
end
|
||||
|
||||
local function block_loop(out,bs,nlit,ndist,littable,disttable)
|
||||
local lit
|
||||
repeat
|
||||
lit = bs:getv(littable,nlit)
|
||||
if lit < 256 then
|
||||
table.insert(out,lit)
|
||||
elseif lit > 256 then
|
||||
local nbits = 0
|
||||
local size = 3
|
||||
local dist = 1
|
||||
if lit < 265 then
|
||||
size = size + lit - 257
|
||||
elseif lit < 285 then
|
||||
nbits = bit.rshift(lit-261,2)
|
||||
size = size + bit.lshift(bit.band(lit-261,3)+4,nbits)
|
||||
else
|
||||
size = 258
|
||||
end
|
||||
if nbits > 0 then
|
||||
size = size + bs:getb(nbits)
|
||||
end
|
||||
local v = bs:getv(disttable,ndist)
|
||||
if v < 4 then
|
||||
dist = dist + v
|
||||
else
|
||||
nbits = bit.rshift(v-2,1)
|
||||
dist = dist + bit.lshift(bit.band(v,1)+2,nbits)
|
||||
dist = dist + bs:getb(nbits)
|
||||
end
|
||||
local p = #out-dist+1
|
||||
while size > 0 do
|
||||
table.insert(out,out[p])
|
||||
p = p + 1
|
||||
size = size - 1
|
||||
end
|
||||
end
|
||||
until lit == 256
|
||||
end
|
||||
|
||||
local function block_dynamic(out,bs)
|
||||
local order = { 17, 18, 19, 1, 9, 8, 10, 7, 11, 6, 12, 5, 13, 4, 14, 3, 15, 2, 16 }
|
||||
local hlit = 257 + bs:getb(5)
|
||||
local hdist = 1 + bs:getb(5)
|
||||
local hclen = 4 + bs:getb(4)
|
||||
local depths = {}
|
||||
for i=1,hclen do
|
||||
local v = bs:getb(3)
|
||||
depths[order[i]] = v
|
||||
end
|
||||
for i=hclen+1,19 do
|
||||
depths[order[i]] = 0
|
||||
end
|
||||
local lengthtable,nlen = hufftable_create(depths)
|
||||
local i=1
|
||||
while i<=hlit+hdist do
|
||||
local v = bs:getv(lengthtable,nlen)
|
||||
if v < 16 then
|
||||
depths[i] = v
|
||||
i = i + 1
|
||||
elseif v < 19 then
|
||||
local nbt = {2,3,7}
|
||||
local nb = nbt[v-15]
|
||||
local c = 0
|
||||
local n = 3 + bs:getb(nb)
|
||||
if v == 16 then
|
||||
c = depths[i-1]
|
||||
elseif v == 18 then
|
||||
n = n + 8
|
||||
end
|
||||
for j=1,n do
|
||||
depths[i] = c
|
||||
i = i + 1
|
||||
end
|
||||
else
|
||||
error("wrong entry in depth table for literal/length alphabet: "..v);
|
||||
end
|
||||
end
|
||||
local litdepths = {} for i=1,hlit do table.insert(litdepths,depths[i]) end
|
||||
local littable,nlit = hufftable_create(litdepths)
|
||||
local distdepths = {} for i=hlit+1,#depths do table.insert(distdepths,depths[i]) end
|
||||
local disttable,ndist = hufftable_create(distdepths)
|
||||
block_loop(out,bs,nlit,ndist,littable,disttable)
|
||||
end
|
||||
|
||||
local function block_static(out,bs)
|
||||
local cnt = { 144, 112, 24, 8 }
|
||||
local dpt = { 8, 9, 7, 8 }
|
||||
local depths = {}
|
||||
for i=1,4 do
|
||||
local d = dpt[i]
|
||||
for j=1,cnt[i] do
|
||||
table.insert(depths,d)
|
||||
end
|
||||
end
|
||||
local littable,nlit = hufftable_create(depths)
|
||||
depths = {}
|
||||
for i=1,32 do
|
||||
depths[i] = 5
|
||||
end
|
||||
local disttable,ndist = hufftable_create(depths)
|
||||
block_loop(out,bs,nlit,ndist,littable,disttable)
|
||||
end
|
||||
|
||||
local function block_uncompressed(out,bs)
|
||||
bs:flushb(bit.band(bs.n,7))
|
||||
local len = bs:getb(16)
|
||||
if bs.n > 0 then
|
||||
error("Unexpected.. should be zero remaining bits in buffer.")
|
||||
end
|
||||
local nlen = bs:getb(16)
|
||||
if bit.bxor(len,nlen) ~= 65535 then
|
||||
error("LEN and NLEN don't match")
|
||||
end
|
||||
for i=bs.pos,bs.pos+len-1 do
|
||||
table.insert(out,bs.buf:byte(i,i))
|
||||
end
|
||||
bs.pos = bs.pos + len
|
||||
end
|
||||
|
||||
function inflate.main(bs)
|
||||
local last,type
|
||||
local output = {}
|
||||
repeat
|
||||
local block
|
||||
last = bs:getb(1)
|
||||
type = bs:getb(2)
|
||||
if type == 0 then
|
||||
block_uncompressed(output,bs)
|
||||
elseif type == 1 then
|
||||
block_static(output,bs)
|
||||
elseif type == 2 then
|
||||
block_dynamic(output,bs)
|
||||
else
|
||||
error("unsupported block type")
|
||||
end
|
||||
until last == 1
|
||||
bs:flushb(bit.band(bs.n,7))
|
||||
return output
|
||||
end
|
||||
|
||||
local crc32_table
|
||||
function inflate.crc32(s,crc)
|
||||
if not crc32_table then
|
||||
crc32_table = {}
|
||||
for i=0,255 do
|
||||
local r=i
|
||||
for j=1,8 do
|
||||
r = bit.bxor(bit.rshift(r,1),bit.band(0xedb88320,bit.bnot(bit.band(r,1)-1)))
|
||||
end
|
||||
crc32_table[i] = r
|
||||
end
|
||||
end
|
||||
crc = bit.bnot(crc or 0)
|
||||
for i=1,#s do
|
||||
local c = s:byte(i)
|
||||
crc = bit.bxor(crc32_table[bit.bxor(c,bit.band(crc,0xff))],bit.rshift(crc,8))
|
||||
end
|
||||
crc = bit.bnot(crc)
|
||||
if crc<0 then
|
||||
-- in Lua < 5.2, sign extension was performed
|
||||
crc = crc + 4294967296
|
||||
end
|
||||
return crc
|
||||
end
|
||||
|
||||
return inflate
|
1
mem.lua
1
mem.lua
|
@ -48,6 +48,7 @@ local _M = {
|
|||
style = 0x12,
|
||||
velocityX = 0x20,
|
||||
velocityY = 0x24,
|
||||
motion = 0x2e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,11 @@ pool.onRenderForm(function(form)
|
|||
end
|
||||
end)
|
||||
|
||||
local DONK_LOAD_POOL = os.getenv('DONK_LOAD_POOL')
|
||||
if DONK_LOAD_POOL ~= nil and DONK_LOAD_POOL ~= "" then
|
||||
pool.requestLoad(DONK_LOAD_POOL)
|
||||
end
|
||||
|
||||
pool.run():next(function()
|
||||
print("The pool finished running!!!")
|
||||
end):catch(function(error)
|
||||
|
|
11
pipe-test.lua
Normal file
11
pipe-test.lua
Normal file
|
@ -0,0 +1,11 @@
|
|||
local pipey = io.open("\\\\.\\pipe\\asoeuth", "r")
|
||||
|
||||
print('reader')
|
||||
|
||||
function on_timer()
|
||||
print('read')
|
||||
print(pipey:read("*l"))
|
||||
set_timer_timeout(100000)
|
||||
end
|
||||
|
||||
set_timer_timeout(100000)
|
101
pool.lua
101
pool.lua
|
@ -1,4 +1,4 @@
|
|||
local callback, set_timer_timeout = callback, set_timer_timeout
|
||||
local callback, set_timer_timeout, zip = callback, set_timer_timeout, zip
|
||||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
|
@ -7,11 +7,9 @@ local Promise = dofile(base.."/promise.lua")
|
|||
local config = dofile(base.."/config.lua")
|
||||
local util = dofile(base.."/util.lua")(Promise)
|
||||
local serpent = dofile(base.."/serpent.lua")
|
||||
local libDeflate = dofile(base.."/LibDeflate.lua")
|
||||
local zzlib = dofile(base.."/zzlib.lua")
|
||||
|
||||
local hasThreads =
|
||||
not util.isWin and
|
||||
config.NeatConfig.Threads > 1
|
||||
local hasThreads = config.NeatConfig.Threads > 1
|
||||
|
||||
-- Only the parent should manage ticks!
|
||||
callback.register('timer', function()
|
||||
|
@ -20,18 +18,14 @@ callback.register('timer', function()
|
|||
end)
|
||||
set_timer_timeout(1)
|
||||
|
||||
local warn = '========== The ROM file to use comes from config.lua.'
|
||||
io.stderr:write(warn)
|
||||
print(warn)
|
||||
|
||||
local Runner = nil
|
||||
if hasThreads then
|
||||
local warn = '========== When using threads, the ROM file to use comes from config.lua. Also, you do not need to start any ROM in the parent process.'
|
||||
io.stderr:write(warn)
|
||||
print(warn)
|
||||
|
||||
Runner = dofile(base.."/runner-wrapper.lua")
|
||||
else
|
||||
local warn = '========== The ROM must already be running when you only have one thread.'
|
||||
io.stderr:write(warn)
|
||||
print(warn)
|
||||
|
||||
Runner = dofile(base.."/runner.lua")
|
||||
end
|
||||
|
||||
|
@ -399,9 +393,7 @@ local function addToSpecies(child)
|
|||
end
|
||||
|
||||
local function initializePool()
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
return util.promiseWrap(function()
|
||||
pool = newPool()
|
||||
|
||||
for i=1,config.NeatConfig.Population do
|
||||
|
@ -419,26 +411,23 @@ local function bytes(x)
|
|||
return string.char(b1,b2,b3,b4)
|
||||
end
|
||||
|
||||
--- Saves the pool to a gzipped Serpent file
|
||||
---@param filename string Filename to write
|
||||
---@return Promise Promise A promise that resolves when the file is saved.
|
||||
local function writeFile(filename)
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function ()
|
||||
local file = io.open(filename, "w")
|
||||
return util.promiseWrap(function ()
|
||||
local file = zip.writer.new(filename)
|
||||
file:create_file('data.serpent')
|
||||
local dump = serpent.dump(pool)
|
||||
local zlib = libDeflate:CompressDeflate(dump)
|
||||
file:write("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00")
|
||||
file:write(zlib)
|
||||
file:write(string.char(0,0,0,0))
|
||||
file:write(bytes(#dump % (2^32)))
|
||||
file:close()
|
||||
file:write(dump)
|
||||
file:close_file()
|
||||
file:commit()
|
||||
end)
|
||||
end
|
||||
|
||||
-- FIXME This isn't technically asynchronous. Probably can't be though.
|
||||
local function loadFile(filename)
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
return util.promiseWrap(function()
|
||||
message("Loading pool from " .. filename, 0x00999900)
|
||||
local file = io.open(filename, "r")
|
||||
if file == nil then
|
||||
|
@ -446,7 +435,8 @@ local function loadFile(filename)
|
|||
return
|
||||
end
|
||||
local contents = file:read("*all")
|
||||
local decomp, _ = libDeflate:DecompressDeflate(contents:sub(11, #contents - 8))
|
||||
file:close()
|
||||
local decomp = zzlib.unzip(contents, 'data.serpent')
|
||||
local ok, obj = serpent.load(decomp)
|
||||
if not ok then
|
||||
message("Error parsing pool file", 0x00990000)
|
||||
|
@ -671,7 +661,15 @@ local function newGeneration()
|
|||
|
||||
pool.generation = pool.generation + 1
|
||||
|
||||
writeFile(_M.saveLoadFile .. ".gen" .. pool.generation .. ".pool")
|
||||
return writeFile(_M.saveLoadFile .. ".gen" .. pool.generation .. ".pool"):next(function()
|
||||
if config.NeatConfig.AutoSave then
|
||||
return writeFile(_M.saveLoadFile)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function reset()
|
||||
return _M.run(true)
|
||||
end
|
||||
|
||||
local runner = Runner(Promise)
|
||||
|
@ -684,6 +682,9 @@ end)
|
|||
runner.onLoad(function(filename)
|
||||
_M.requestLoad(filename)
|
||||
end)
|
||||
runner.onReset(function(filename)
|
||||
_M.requestReset()
|
||||
end)
|
||||
runner.onRenderForm(function(form)
|
||||
processRenderForm(form)
|
||||
end)
|
||||
|
@ -691,17 +692,16 @@ end)
|
|||
local playTop = nil
|
||||
local topRequested = false
|
||||
|
||||
local loadRequested = false
|
||||
local loadRequested = config.NeatConfig.AutoSave
|
||||
local saveRequested = false
|
||||
local resetRequested = false
|
||||
local function mainLoop(currentSpecies, topGenome)
|
||||
if currentSpecies == nil then
|
||||
currentSpecies = 1
|
||||
end
|
||||
|
||||
local slice = pool.species[currentSpecies]
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
return util.promiseWrap(function()
|
||||
if loadRequested then
|
||||
loadRequested = false
|
||||
currentSpecies = nil
|
||||
|
@ -714,6 +714,11 @@ local function mainLoop(currentSpecies, topGenome)
|
|||
return savePool()
|
||||
end
|
||||
|
||||
if resetRequested then
|
||||
resetRequested = false
|
||||
return reset()
|
||||
end
|
||||
|
||||
if topRequested then
|
||||
topRequested = false
|
||||
return playTop()
|
||||
|
@ -724,16 +729,8 @@ local function mainLoop(currentSpecies, topGenome)
|
|||
end
|
||||
|
||||
if hasThreads then
|
||||
slice = {}
|
||||
for i=currentSpecies, currentSpecies + config.NeatConfig.Threads - 1, 1 do
|
||||
if pool.species[i] == nil then
|
||||
break
|
||||
end
|
||||
|
||||
table.insert(slice, pool.species[i])
|
||||
end
|
||||
slice = pool.species
|
||||
end
|
||||
local finished = 0
|
||||
|
||||
return runner.run(
|
||||
slice,
|
||||
|
@ -742,7 +739,11 @@ local function mainLoop(currentSpecies, topGenome)
|
|||
-- Genome callback
|
||||
-- FIXME Should we do something here??? What was your plan, past me?
|
||||
end
|
||||
):next(function()
|
||||
):next(function(maxFitness)
|
||||
if maxFitness > pool.maxFitness then
|
||||
pool.maxFitness = maxFitness
|
||||
end
|
||||
|
||||
if hasThreads then
|
||||
currentSpecies = currentSpecies + #slice
|
||||
else
|
||||
|
@ -788,6 +789,10 @@ function _M.requestSave(filename)
|
|||
saveRequested = true
|
||||
end
|
||||
|
||||
function _M.requestReset()
|
||||
resetRequested = true
|
||||
end
|
||||
|
||||
function _M.onMessage(handler)
|
||||
table.insert(_M.onMessageHandler, handler)
|
||||
end
|
||||
|
@ -812,8 +817,12 @@ function _M.run(reset)
|
|||
return promise:next(function()
|
||||
return writeFile(config.PoolDir.."temp.pool")
|
||||
end):next(function ()
|
||||
if not hasThreads then
|
||||
return util.loadAndStart(config.ROM)
|
||||
end
|
||||
end):next(function()
|
||||
return mainLoop()
|
||||
end)
|
||||
end
|
||||
|
||||
return _M
|
||||
return _M
|
BIN
pool/MainbraceMayhem.lsmv
Normal file
BIN
pool/MainbraceMayhem.lsmv
Normal file
Binary file not shown.
BIN
pool/MainbraceMayhemBonus.lsmv
Normal file
BIN
pool/MainbraceMayhemBonus.lsmv
Normal file
Binary file not shown.
BIN
pool/MainbraceMayhemTopOfRope.lsmv
Normal file
BIN
pool/MainbraceMayhemTopOfRope.lsmv
Normal file
Binary file not shown.
|
@ -10,16 +10,10 @@ callback.register('timer', function()
|
|||
end)
|
||||
set_timer_timeout(1)
|
||||
|
||||
|
||||
local Runner = dofile(base.."/runner.lua")
|
||||
local serpent = dofile(base.."/serpent.lua")
|
||||
local util = dofile(base.."/util.lua")(Promise)
|
||||
|
||||
local inputFilePath = os.getenv("RUNNER_INPUT_FILE")
|
||||
local outputFilePath = os.getenv("RUNNER_OUTPUT_FILE")
|
||||
|
||||
local outContents = {}
|
||||
|
||||
local statusLine = nil
|
||||
local statusColor = 0x0000ff00
|
||||
|
||||
|
@ -27,20 +21,45 @@ local species = nil
|
|||
local speciesId = -1
|
||||
local generationIndex = nil
|
||||
|
||||
local inputPipeName = os.getenv("RUNNER_INPUT_PIPE")
|
||||
local outputPipeName = os.getenv("RUNNER_OUTPUT_PIPE")
|
||||
|
||||
print('Opening input pipe '..inputPipeName)
|
||||
local inputPipe = util.openReadPipe(inputPipeName)
|
||||
if inputPipe == nil then
|
||||
error('Error opening input file')
|
||||
end
|
||||
print('Opened input pipe '..inputPipeName)
|
||||
|
||||
print('Opening output file '..outputPipeName)
|
||||
local outputPipe = util.openReadPipeWriter(outputPipeName)
|
||||
print('Opened output file '..outputPipeName)
|
||||
|
||||
local function writeResponse(object)
|
||||
outputPipe:write(serpent.dump(object).."\n")
|
||||
outputPipe:flush()
|
||||
end
|
||||
|
||||
local function unblockLoop()
|
||||
return util.delay(1000000):next(function()
|
||||
outputPipe:write(".\n")
|
||||
outputPipe:flush()
|
||||
return unblockLoop()
|
||||
end)
|
||||
end
|
||||
|
||||
local runner = Runner(Promise)
|
||||
runner.onMessage(function(msg, color)
|
||||
statusLine = msg
|
||||
statusColor = color
|
||||
print(msg)
|
||||
table.insert(
|
||||
outContents,
|
||||
serpent.dump({
|
||||
type = 'onMessage',
|
||||
speciesId = speciesId,
|
||||
msg = msg,
|
||||
color = color,
|
||||
})
|
||||
)
|
||||
|
||||
writeResponse({
|
||||
type = 'onMessage',
|
||||
speciesId = speciesId,
|
||||
msg = msg,
|
||||
color = color,
|
||||
})
|
||||
end)
|
||||
|
||||
runner.onRenderForm(function(form)
|
||||
|
@ -60,115 +79,88 @@ runner.onRenderForm(function(form)
|
|||
end)
|
||||
|
||||
runner.onSave(function(filename)
|
||||
table.insert(
|
||||
outContents,
|
||||
serpent.dump({
|
||||
type = 'onSave',
|
||||
filename = filename,
|
||||
speciesId = speciesId,
|
||||
})
|
||||
)
|
||||
writeResponse({
|
||||
type = 'onSave',
|
||||
filename = filename,
|
||||
speciesId = speciesId,
|
||||
})
|
||||
end)
|
||||
|
||||
runner.onLoad(function(filename)
|
||||
table.insert(
|
||||
outContents,
|
||||
serpent.dump({
|
||||
type = 'onLoad',
|
||||
filename = filename,
|
||||
speciesId = speciesId,
|
||||
})
|
||||
)
|
||||
writeResponse({
|
||||
type = 'onLoad',
|
||||
filename = filename,
|
||||
speciesId = speciesId,
|
||||
})
|
||||
end)
|
||||
|
||||
local function waitLoop()
|
||||
local inputData = nil
|
||||
local ok = false
|
||||
while not ok or inputData == nil or speciesId == inputData[1].id do
|
||||
local inputFile = io.open(inputFilePath, 'r')
|
||||
ok, inputData = serpent.load(inputFile:read('*a'))
|
||||
inputFile:close()
|
||||
runner.onReset(function()
|
||||
writeResponse({
|
||||
type = 'onReset',
|
||||
speciesId = speciesId,
|
||||
})
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
print("Deserialization error")
|
||||
local function waitLoop(inputLine)
|
||||
return util.promiseWrap(function()
|
||||
local ok, inputData = serpent.load(inputLine)
|
||||
|
||||
if not ok or inputData == nil then
|
||||
io.stderr:write("Deserialization error\n")
|
||||
io.stderr:write(inputLine.."\n")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
print('Received input from master process')
|
||||
print('Received input from master process')
|
||||
|
||||
species = inputData[1]
|
||||
species = inputData[1]
|
||||
|
||||
speciesId = species.id
|
||||
speciesId = species.id
|
||||
|
||||
generationIndex = inputData[2]
|
||||
generationIndex = inputData[2]
|
||||
|
||||
outContents = {}
|
||||
print('Running')
|
||||
|
||||
print('Running')
|
||||
|
||||
return runner.run(
|
||||
species,
|
||||
generationIndex,
|
||||
function(genome, index)
|
||||
table.insert(
|
||||
outContents,
|
||||
serpent.dump({
|
||||
return runner.run(
|
||||
species,
|
||||
generationIndex,
|
||||
function(genome, index)
|
||||
writeResponse({
|
||||
type = 'onGenome',
|
||||
genome = genome,
|
||||
genomeIndex = index,
|
||||
speciesId = speciesId,
|
||||
})
|
||||
)
|
||||
end
|
||||
):next(function()
|
||||
table.insert(
|
||||
outContents,
|
||||
serpent.dump({
|
||||
end
|
||||
):next(function(maxFitness)
|
||||
writeResponse({
|
||||
type = 'onFinish',
|
||||
maxFitness = maxFitness,
|
||||
speciesId = speciesId,
|
||||
})
|
||||
)
|
||||
|
||||
-- Truncate the input file to reduce the amount of time
|
||||
-- wasted if we reopen it too early
|
||||
local inputFile = io.open(inputFilePath, "w")
|
||||
inputFile:close()
|
||||
|
||||
local waiter = nil
|
||||
if util.isWin then
|
||||
waiter = Promise.new()
|
||||
waiter:resolve()
|
||||
else
|
||||
waiter = util.waitForFiles(inputFilePath)[1]
|
||||
end
|
||||
|
||||
-- Write the result
|
||||
local outFile = io.open(outputFilePath, "w")
|
||||
outFile:write(table.concat(outContents, "\n"))
|
||||
outFile:close()
|
||||
|
||||
return waiter
|
||||
end)
|
||||
end):next(function()
|
||||
return inputPipe:read("*l")
|
||||
end):next(waitLoop)
|
||||
end
|
||||
|
||||
local waiter = nil
|
||||
if util.isWin then
|
||||
waiter = Promise.new()
|
||||
waiter:resolve()
|
||||
else
|
||||
waiter = util.waitForFiles(inputFilePath)[1]
|
||||
end
|
||||
|
||||
local sec, usec = utime()
|
||||
local ts = sec * 1000000 + usec
|
||||
|
||||
local outFile = io.open(outputFilePath, "w")
|
||||
outFile:write(serpent.dump({ type = 'onInit', ts = ts }))
|
||||
outFile:close()
|
||||
local waiter = util.promiseWrap(function()
|
||||
return inputPipe:read("*l")
|
||||
end)
|
||||
|
||||
writeResponse({ type = 'onInit', ts = ts })
|
||||
|
||||
print(string.format('Wrote init to output at %d', ts))
|
||||
|
||||
waiter:next(waitLoop):catch(function(error)
|
||||
waiter:next(function(inputLine)
|
||||
return waitLoop(inputLine)
|
||||
end):catch(function(error)
|
||||
if type(error) == "table" then
|
||||
error = "\n"..table.concat(error, "\n")
|
||||
end
|
||||
print('Runner process error: '..error)
|
||||
io.stderr:write('Runner process error: '..error..'\n')
|
||||
end)
|
||||
end)
|
|
@ -7,28 +7,13 @@ local Promise = nil
|
|||
local util = nil
|
||||
local config = dofile(base.."/config.lua")
|
||||
local serpent = dofile(base.."/serpent.lua")
|
||||
local temps = {
|
||||
os.getenv("TMPDIR"),
|
||||
os.getenv("TEMP"),
|
||||
os.getenv("TEMPDIR"),
|
||||
os.getenv("TMP"),
|
||||
}
|
||||
|
||||
local tempDir = "/tmp"
|
||||
for i=1,#temps,1 do
|
||||
local temp = temps[i]
|
||||
if temp ~= nil and temp ~= "" then
|
||||
tempDir = temps[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local tmpFileName = tempDir.."/donk_runner_"..
|
||||
local pipePrefix = "donk_runner_"..
|
||||
string.hex(math.floor(random.integer(0, 0xffffffff)))..
|
||||
string.hex(math.floor(random.integer(0, 0xffffffff)))
|
||||
|
||||
local inputPrefix = tmpFileName..'_input_'
|
||||
local outputPrefix = tmpFileName..'_output_'
|
||||
local inputPrefix = pipePrefix..'_input_'
|
||||
local outputPrefix = pipePrefix..'_output_'
|
||||
|
||||
local function message(_M, msg, color)
|
||||
if color == nil then
|
||||
|
@ -60,6 +45,16 @@ local function onLoad(_M, handler)
|
|||
table.insert(_M.onLoadHandler, handler)
|
||||
end
|
||||
|
||||
local function reset(_M)
|
||||
for i=#_M.onResetHandler,1,-1 do
|
||||
_M.onResetHandler[i]()
|
||||
end
|
||||
end
|
||||
|
||||
local function onReset(_M, handler)
|
||||
table.insert(_M.onResetHandler, handler)
|
||||
end
|
||||
|
||||
local function onMessage(_M, handler)
|
||||
table.insert(_M.onMessageHandler, handler)
|
||||
end
|
||||
|
@ -69,34 +64,44 @@ end
|
|||
---@param count integer Number of processes needed
|
||||
---@return Promise Promise A promise that resolves when all the processes are ready
|
||||
local function launchChildren(_M, count)
|
||||
local children = {}
|
||||
while #_M.poppets < count do
|
||||
local i = #_M.poppets+1
|
||||
local outputFileName = outputPrefix..i
|
||||
local inputFileName = inputPrefix..i
|
||||
local promises = {}
|
||||
for i=#_M.poppets+1,count,1 do
|
||||
local newOne = {
|
||||
process = nil,
|
||||
output = util.openReadPipe(outputPrefix..i),
|
||||
input = nil,
|
||||
}
|
||||
|
||||
local outputPipeName = outputPrefix..i
|
||||
local inputPipeName = inputPrefix..i
|
||||
|
||||
local settingsDir = nil
|
||||
if util.isWin then
|
||||
settingsDir = tempDir.."/donk_runner_settings_"..i
|
||||
settingsDir = util.getTempDir().."/donk_runner_settings_"..i
|
||||
util.mkdir(settingsDir)
|
||||
end
|
||||
|
||||
local envs = {
|
||||
RUNNER_INPUT_FILE = inputFileName,
|
||||
RUNNER_OUTPUT_FILE = outputFileName,
|
||||
RUNNER_INPUT_PIPE = inputPipeName,
|
||||
RUNNER_OUTPUT_PIPE = outputPipeName,
|
||||
APPDATA = settingsDir,
|
||||
}
|
||||
|
||||
local child = util.waitForFiles(outputFileName)[1]
|
||||
|
||||
local cmd = '"'.._M.hostProcess..'" "--rom='..config.ROM..'" --unpause "--lua='..base..'/runner-process.lua"'
|
||||
local poppet = util.popenCmd(cmd, nil, envs)
|
||||
table.insert(_M.poppets, poppet)
|
||||
newOne.process = util.popenCmd(cmd, nil, envs)
|
||||
|
||||
table.insert(children, child)
|
||||
-- Wait for init
|
||||
local promise = util.promiseWrap(function()
|
||||
newOne.output:read("*l")
|
||||
while newOne.input == nil do
|
||||
newOne.input = util.openReadPipeWriter(inputPipeName)
|
||||
end
|
||||
end)
|
||||
table.insert(promises, promise)
|
||||
table.insert(_M.poppets, newOne)
|
||||
end
|
||||
|
||||
return Promise.all(table.unpack(children))
|
||||
return Promise.all(table.unpack(promises))
|
||||
end
|
||||
|
||||
return function(promise)
|
||||
|
@ -107,13 +112,14 @@ return function(promise)
|
|||
end
|
||||
-- FIXME Maybe don't do this in the "constructor"?
|
||||
if util.isWin then
|
||||
util.downloadFile('https://github.com/watchexec/watchexec/releases/download/1.13.1/watchexec-1.13.1-x86_64-pc-windows-gnu.zip', base..'/watchexec.zip')
|
||||
util.unzip(base..'/watchexec.zip', base)
|
||||
os.rename(base..'watchexec-1.13.1-x86_64-pc-windows-gnu', base..'/watchexec')
|
||||
util.downloadFile("https://github.com/psmay/windows-named-pipe-utils/releases/download/v0.1.1/build.zip", base.."/namedpipe.zip")
|
||||
util.unzip(base.."/namedpipe.zip", base)
|
||||
os.rename(base.."/build", "namedpipe")
|
||||
end
|
||||
|
||||
local _M = {
|
||||
onMessageHandler = {},
|
||||
onResetHandler = {},
|
||||
onSaveHandler = {},
|
||||
onLoadHandler = {},
|
||||
poppets = {},
|
||||
|
@ -148,86 +154,109 @@ return function(promise)
|
|||
onLoad(_M, handler)
|
||||
end
|
||||
|
||||
_M.run = function(speciesSlice, generationIdx, genomeCallback)
|
||||
_M.onReset = function(handler)
|
||||
onReset(_M, handler)
|
||||
end
|
||||
|
||||
_M.run = function(species, generationIdx, genomeCallback)
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
-- Create the input files and output files
|
||||
for i=1,#speciesSlice,1 do
|
||||
local inputFileName = inputPrefix..i
|
||||
local inputFile = io.open(inputFileName, 'a')
|
||||
inputFile:close()
|
||||
|
||||
local outputFileName = outputPrefix..i
|
||||
local outputFile = io.open(outputFileName, 'a')
|
||||
outputFile:close()
|
||||
end
|
||||
|
||||
return launchChildren(_M, #speciesSlice)
|
||||
return launchChildren(_M, config.NeatConfig.Threads)
|
||||
end):next(function()
|
||||
local outputFileNames = {}
|
||||
for i=1,#speciesSlice,1 do
|
||||
table.insert(outputFileNames, outputPrefix..i)
|
||||
end
|
||||
|
||||
local waiters = util.waitForFiles(outputFileNames)
|
||||
|
||||
message(_M, 'Setting up child processes')
|
||||
|
||||
for i=1,#speciesSlice,1 do
|
||||
local inputFileName = tmpFileName.."_input_"..i
|
||||
local inputFile = io.open(inputFileName, 'w')
|
||||
inputFile:write(serpent.dump({speciesSlice[i], generationIdx}))
|
||||
inputFile:close()
|
||||
local maxFitness = nil
|
||||
local function readLoop(outputPipe)
|
||||
return util.promiseWrap(function()
|
||||
return outputPipe:read("*l")
|
||||
end):next(function(line)
|
||||
if line == nil or line == "" then
|
||||
util.closeCmd(outputPipe)
|
||||
end
|
||||
|
||||
local ok, obj = serpent.load(line)
|
||||
if not ok then
|
||||
return false
|
||||
end
|
||||
|
||||
if obj == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
if obj.type == 'onMessage' then
|
||||
message(_M, obj.msg, obj.color)
|
||||
elseif obj.type == 'onLoad' then
|
||||
load(_M, obj.filename)
|
||||
elseif obj.type == 'onSave' then
|
||||
save(_M, obj.filename)
|
||||
elseif obj.type == 'onReset' then
|
||||
reset(_M)
|
||||
elseif obj.type == 'onGenome' then
|
||||
for i=1,#species,1 do
|
||||
local s = species[i]
|
||||
if s.id == obj.speciesId then
|
||||
message(_M, string.format('Write Species %d Genome %d', obj.speciesId, obj.genomeIndex))
|
||||
s.genomes[obj.genomeIndex] = obj.genome
|
||||
break
|
||||
end
|
||||
end
|
||||
genomeCallback(obj.genome, obj.index)
|
||||
elseif obj.type == 'onFinish' then
|
||||
if maxFitness == nil or obj.maxFitness > maxFitness then
|
||||
maxFitness = obj.maxFitness
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
end):next(function(finished)
|
||||
if finished then
|
||||
return maxFitness
|
||||
end
|
||||
|
||||
return readLoop(outputPipe)
|
||||
end)
|
||||
end
|
||||
|
||||
local waiters = {}
|
||||
for t=1,config.NeatConfig.Threads,1 do
|
||||
waiters[t] = Promise.new()
|
||||
waiters[t]:resolve()
|
||||
end
|
||||
|
||||
local currentSpecies = 1
|
||||
while currentSpecies < #species do
|
||||
for t=1,config.NeatConfig.Threads,1 do
|
||||
local s = species[currentSpecies]
|
||||
if s == nil then
|
||||
break
|
||||
end
|
||||
|
||||
local inputPipe = _M.poppets[t].input
|
||||
local outputPipe = _M.poppets[t].output
|
||||
waiters[t] = waiters[t]:next(function()
|
||||
inputPipe:write(serpent.dump({s, generationIdx}).."\n")
|
||||
inputPipe:flush()
|
||||
|
||||
return readLoop(outputPipe)
|
||||
end)
|
||||
currentSpecies = currentSpecies + 1
|
||||
end
|
||||
end
|
||||
|
||||
message(_M, 'Waiting for child processes to finish')
|
||||
|
||||
for i=1,#waiters,1 do
|
||||
waiters[i] = waiters[i]:next(function(outputFileName)
|
||||
message(_M, "Processing output "..i)
|
||||
local outputFile = io.open(outputFileName, "r")
|
||||
local line = ""
|
||||
repeat
|
||||
local ok, obj = serpent.load(line)
|
||||
if not ok then
|
||||
goto continue
|
||||
end
|
||||
|
||||
if obj == nil then
|
||||
goto continue
|
||||
end
|
||||
|
||||
if obj.type == 'onMessage' then
|
||||
message(_M, obj.msg, obj.color)
|
||||
elseif obj.type == 'onLoad' then
|
||||
load(_M, obj.filename)
|
||||
elseif obj.type == 'onSave' then
|
||||
save(_M, obj.filename)
|
||||
elseif obj.type == 'onGenome' then
|
||||
for i=1,#speciesSlice,1 do
|
||||
local s = speciesSlice[i]
|
||||
if s.id == obj.speciesId then
|
||||
s.genomes[obj.genomeIndex] = obj.genome
|
||||
break
|
||||
end
|
||||
end
|
||||
genomeCallback(obj.genome, obj.index)
|
||||
elseif obj.type == 'onFinish' then
|
||||
message(_M, "Finished processing output "..i)
|
||||
return
|
||||
end
|
||||
|
||||
::continue::
|
||||
line = outputFile:read()
|
||||
until(line == "" or line == nil)
|
||||
error("Child process "..i.." never finished")
|
||||
end)
|
||||
end
|
||||
|
||||
return Promise.all(table.unpack(waiters))
|
||||
end):next(function()
|
||||
end):next(function(maxFitnesses)
|
||||
message(_M, 'Child processes finished')
|
||||
local maxestFitness = maxFitnesses[1]
|
||||
for i=1,#maxFitnesses,1 do
|
||||
local maxFitness = maxFitnesses[i]
|
||||
if maxFitness > maxestFitness then
|
||||
maxestFitness = maxFitness
|
||||
end
|
||||
end
|
||||
return maxestFitness
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
207
runner.lua
207
runner.lua
|
@ -1,7 +1,7 @@
|
|||
local gui, input, movie, settings, exec, callback, set_timer_timeout = gui, input, movie, settings, exec, callback, set_timer_timeout
|
||||
local mem = require "mem"
|
||||
local gui, input, movie, settings, exec, callback, set_timer_timeout, memory, bsnes = gui, input, movie, settings, exec, callback, set_timer_timeout, memory, bsnes
|
||||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
local Promise = nil
|
||||
|
||||
local config = dofile(base.."/config.lua")
|
||||
|
@ -255,10 +255,21 @@ local function displayButtons(_M)
|
|||
gui.renderctx.setnull()
|
||||
end
|
||||
|
||||
local function getDistanceTraversed(areaInfos)
|
||||
local distanceTraversed = 0
|
||||
for _,areaInfo in pairs(areaInfos) do
|
||||
for i=1,#areaInfo.waypoints,1 do
|
||||
local waypoint = areaInfo.waypoints[i]
|
||||
distanceTraversed = distanceTraversed + (waypoint.startDistance - waypoint.shortest)
|
||||
end
|
||||
end
|
||||
return distanceTraversed
|
||||
end
|
||||
|
||||
local formCtx = nil
|
||||
local form = nil
|
||||
local function displayForm(_M)
|
||||
if #_M.onRenderFormHandler == 0 then
|
||||
if config.NeatConfig.ShowInterface == false or #_M.onRenderFormHandler == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -275,28 +286,30 @@ local function displayForm(_M)
|
|||
gui.rectangle(0, 0, 500, guiHeight, 1, 0x00ffffff, 0xbb000000)
|
||||
--gui.circle(game.screenX-84, game.screenY-84, 192 / 2, 1, 0x50000000)
|
||||
|
||||
local distanceTraversed = getDistanceTraversed(_M.areaInfo)
|
||||
local goalX = 0
|
||||
local goalY = 0
|
||||
local areaInfo = _M.areaInfo[_M.currentArea]
|
||||
local distanceTraversed = 0
|
||||
if areaInfo ~= nil then
|
||||
distanceTraversed = areaInfo.startDistance - areaInfo.shortest
|
||||
goalX = areaInfo.preferredExit.x
|
||||
goalY = areaInfo.preferredExit.y
|
||||
end
|
||||
|
||||
gui.text(5, 30, "Timeout: " .. _M.timeout)
|
||||
gui.text(5, 5, "Generation: " .. _M.currentGenerationIndex)
|
||||
gui.text(130, 5, "Species: " .. _M.currentSpecies.id)
|
||||
gui.text(230, 5, "Genome: " .. _M.currentGenomeIndex)
|
||||
gui.text(130, 30, "Max: " .. math.floor(_M.maxFitness))
|
||||
--gui.text(330, 5, "Measured: " .. math.floor(measured/total*100) .. "%")
|
||||
gui.text(5, 65, "Bananas: " .. _M.totalBananas)
|
||||
gui.text(5, 80, "KONG: " .. (game.getKong() - _M.startKong))
|
||||
gui.text(5, 95, "Krem: " .. (game.getKremCoins() - _M.startKrem))
|
||||
gui.text(130, 65, "Coins: " .. (game.getCoins() - _M.startCoins))
|
||||
gui.text(130, 80, "Lives: " .. game.getLives())
|
||||
gui.text(130, 95, "Bumps: " .. _M.bumps)
|
||||
gui.text(230, 65, "Damage: " .. _M.partyHitCounter)
|
||||
gui.text(230, 80, "PowerUp: " .. _M.powerUpCounter)
|
||||
gui.text(320, 65, string.format("Current Area: %04x", _M.currentArea))
|
||||
gui.text(320, 80, string.format("Traveled: %d", distanceTraversed))
|
||||
gui.text(5, 5, string.format([[
|
||||
Generation: %4d Species: %4d Genome: %4d
|
||||
|
||||
Timeout: %4d Max: %6d
|
||||
|
||||
Bananas: %4d Coins: %3d Damage: %3d Current area: %04x
|
||||
KONG: %7d Lives: %3d Powerup: %2d Traveled: %8d
|
||||
Krem: %7d Bumps: %3d Goal Offset: %8d, %7d
|
||||
]],
|
||||
_M.currentGenerationIndex, _M.currentSpecies.id, _M.currentGenomeIndex,
|
||||
_M.timeout, math.floor(_M.maxFitness),
|
||||
_M.totalBananas, game.getCoins() - _M.startCoins, _M.partyHitCounter, _M.currentArea,
|
||||
game.getKong() - _M.startKong, game.getLives(), _M.powerUpCounter, distanceTraversed,
|
||||
game.getKremCoins() - _M.startKrem, _M.bumps, goalX - game.partyX, goalY - game.partyY
|
||||
))
|
||||
|
||||
displayButtons(_M)
|
||||
formCtx:set()
|
||||
|
@ -330,7 +343,6 @@ local function evaluateNetwork(_M, network, inputs, inputDeltas)
|
|||
message(_M, "Incorrect number of neural network inputs.", 0x00990000)
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
for i=1,Inputs do
|
||||
network.neurons[i].value = inputs[i] * inputDeltas[i]
|
||||
|
@ -382,12 +394,9 @@ end
|
|||
local frame = 0
|
||||
local lastFrame = 0
|
||||
|
||||
local function evaluateCurrent(_M)
|
||||
local function evaluateCurrent(_M, inputs, inputDeltas)
|
||||
local genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
|
||||
|
||||
local inputDeltas = {}
|
||||
local inputs, inputDeltas = game.getInputs()
|
||||
|
||||
controller = evaluateNetwork(_M, genome.network, inputs, inputDeltas)
|
||||
|
||||
if controller[6] and controller[7] then
|
||||
|
@ -452,9 +461,9 @@ local function generateNetwork(genome)
|
|||
genome.network = network
|
||||
end
|
||||
|
||||
local rew = movie.to_rewind(config.NeatConfig.Filename)
|
||||
local beginRewindState = nil
|
||||
local function rewind()
|
||||
return game.rewind(rew):next(function()
|
||||
return game.rewind(beginRewindState):next(function()
|
||||
frame = 0
|
||||
lastFrame = 0
|
||||
end)
|
||||
|
@ -471,7 +480,20 @@ local function initializeRun(_M)
|
|||
exec('enable-sound '..enableSound)
|
||||
gui.subframe_update(false)
|
||||
|
||||
return rewind():next(function(preferredExit)
|
||||
return rewind():next(function()
|
||||
bsnes.enablelayer(0, 0, true)
|
||||
bsnes.enablelayer(0, 1, false)
|
||||
bsnes.enablelayer(1, 0, false)
|
||||
bsnes.enablelayer(1, 1, false)
|
||||
bsnes.enablelayer(2, 0, false)
|
||||
bsnes.enablelayer(2, 1, false)
|
||||
bsnes.enablelayer(3, 0, false)
|
||||
bsnes.enablelayer(3, 1, false)
|
||||
bsnes.enablelayer(4, 0, true)
|
||||
bsnes.enablelayer(4, 1, true)
|
||||
bsnes.enablelayer(4, 2, true)
|
||||
bsnes.enablelayer(4, 3, true)
|
||||
|
||||
if config.StartPowerup ~= nil then
|
||||
game.writePowerup(config.StartPowerup)
|
||||
end
|
||||
|
@ -500,22 +522,19 @@ local function initializeRun(_M)
|
|||
_M.currentArea = game.getCurrentArea()
|
||||
_M.lastArea = _M.currentArea
|
||||
|
||||
for areaId,areaInfo in pairs(_M.areaInfo) do
|
||||
areaInfo.shortest = areaInfo.startDistance
|
||||
for _,areaInfo in pairs(_M.areaInfo) do
|
||||
for i=1,#areaInfo.waypoints,1 do
|
||||
local waypoint = areaInfo.waypoints[i]
|
||||
waypoint.shortest = waypoint.startDistance
|
||||
end
|
||||
end
|
||||
|
||||
local genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
|
||||
generateNetwork(genome)
|
||||
evaluateCurrent(_M)
|
||||
end)
|
||||
end
|
||||
|
||||
local function getDistanceTraversed(areaInfo)
|
||||
local distanceTraversed = 0
|
||||
for areaId,areaInfo in pairs(areaInfo) do
|
||||
distanceTraversed = areaInfo.startDistance - areaInfo.shortest
|
||||
end
|
||||
return distanceTraversed
|
||||
local inputs, inputDeltas = game.getInputs()
|
||||
evaluateCurrent(_M, inputs, inputDeltas)
|
||||
end)
|
||||
end
|
||||
|
||||
local function mainLoop(_M, genome)
|
||||
|
@ -524,6 +543,7 @@ local function mainLoop(_M, genome)
|
|||
if nextArea ~= _M.lastArea then
|
||||
_M.lastArea = nextArea
|
||||
game.onceAreaLoaded(function()
|
||||
message(_M, 'Loaded area '..nextArea)
|
||||
_M.timeout = _M.timeout + 60 * 5
|
||||
_M.currentArea = nextArea
|
||||
_M.lastArea = _M.currentArea
|
||||
|
@ -531,12 +551,28 @@ local function mainLoop(_M, genome)
|
|||
elseif _M.currentArea == _M.lastArea and _M.areaInfo[_M.currentArea] == nil then
|
||||
message(_M, 'Searching for the main exit in this area')
|
||||
return game.findPreferredExit():next(function(preferredExit)
|
||||
local startDistance = math.floor(math.sqrt((preferredExit.y - game.partyY) ^ 2 + (preferredExit.x - game.partyX) ^ 2))
|
||||
_M.areaInfo[_M.currentArea] = {
|
||||
startDistance = startDistance,
|
||||
local areaInfo = {
|
||||
preferredExit = preferredExit,
|
||||
shortest = startDistance,
|
||||
waypoints = game.getWaypoints(preferredExit.x, preferredExit.y),
|
||||
}
|
||||
table.insert(areaInfo.waypoints, 1, preferredExit)
|
||||
|
||||
for i=#areaInfo.waypoints,1,-1 do
|
||||
local waypoint = areaInfo.waypoints[i]
|
||||
if waypoint.y > game.partyY + mem.size.tile * 7 then
|
||||
message(_M, string.format('Skipped waypoint %d,%d', waypoint.x, waypoint.y), 0x00ffff00)
|
||||
table.remove(areaInfo.waypoints, i)
|
||||
goto continue
|
||||
end
|
||||
local startDistance = math.floor(math.sqrt((waypoint.y - game.partyY) ^ 2 + (waypoint.x - game.partyX) ^ 2))
|
||||
waypoint.startDistance = startDistance
|
||||
waypoint.shortest = startDistance
|
||||
::continue::
|
||||
end
|
||||
|
||||
message(_M, string.format('Found %d waypoints', #areaInfo.waypoints))
|
||||
|
||||
_M.areaInfo[_M.currentArea] = areaInfo
|
||||
end)
|
||||
end
|
||||
end):next(function()
|
||||
|
@ -555,34 +591,34 @@ local function mainLoop(_M, genome)
|
|||
displayGenome(genome)
|
||||
end
|
||||
|
||||
if _M.currentFrame%5 == 0 then
|
||||
evaluateCurrent(_M)
|
||||
end
|
||||
|
||||
game.getPositions()
|
||||
local timeoutConst = 0
|
||||
if game.vertical then
|
||||
timeoutConst = config.NeatConfig.TimeoutConstant * 10
|
||||
else
|
||||
timeoutConst = config.NeatConfig.TimeoutConstant
|
||||
local timeoutConst = config.NeatConfig.TimeoutConstant
|
||||
|
||||
local fell = game.fell()
|
||||
if (fell or game.diedFromHit()) and _M.timeout > 0 then
|
||||
_M.timeout = 0
|
||||
end
|
||||
|
||||
-- Don't punish being launched by barrels
|
||||
-- FIXME Will this skew mine cart levels?
|
||||
if game.getVelocityY() < -2104 then
|
||||
message(_M, "BARREL! ".._M.drawFrame, 0x00ffff00)
|
||||
if _M.timeout < timeoutConst + 60 * 12 then
|
||||
_M.timeout = _M.timeout + 60 * 12
|
||||
if _M.currentFrame % 5 == 0 then
|
||||
local sprites = game.getSprites()
|
||||
local inputs, inputDeltas = game.getInputs(sprites)
|
||||
if game.bonusScreenDisplayed(inputs) and _M.timeout > -1000 and _M.timeout < timeoutConst then
|
||||
_M.timeout = timeoutConst
|
||||
end
|
||||
|
||||
evaluateCurrent(_M, inputs, inputDeltas)
|
||||
end
|
||||
|
||||
local areaInfo = _M.areaInfo[_M.currentArea]
|
||||
if areaInfo ~= nil and game.partyY ~= 0 and game.partyX ~= 0 then
|
||||
local exitDist = math.floor(math.sqrt((areaInfo.preferredExit.y - game.partyY) ^ 2 + (areaInfo.preferredExit.x - game.partyX) ^ 2))
|
||||
if exitDist < areaInfo.shortest then
|
||||
areaInfo.shortest = exitDist
|
||||
if _M.timeout < timeoutConst then
|
||||
_M.timeout = timeoutConst
|
||||
for i=1,#areaInfo.waypoints,1 do
|
||||
local waypoint = areaInfo.waypoints[i]
|
||||
local dist = math.floor(math.sqrt((waypoint.y - game.partyY) ^ 2 + (waypoint.x - game.partyX) ^ 2))
|
||||
if dist < waypoint.shortest then
|
||||
waypoint.shortest = dist
|
||||
if _M.timeout < timeoutConst then
|
||||
_M.timeout = timeoutConst
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -648,7 +684,12 @@ local function mainLoop(_M, genome)
|
|||
|
||||
local distanceTraversed = getDistanceTraversed(_M.areaInfo) - _M.currentFrame / 2
|
||||
|
||||
local fitness = bananaCoinsFitness - bumpPenalty - hitPenalty + powerUpBonus + distanceTraversed + game.getJumpHeight() / 100
|
||||
local fitness = bananaCoinsFitness - bumpPenalty - hitPenalty + powerUpBonus + distanceTraversed
|
||||
|
||||
if fell then
|
||||
fitness = fitness / 10
|
||||
message(_M, "Fall penalty 1/10")
|
||||
end
|
||||
|
||||
local lives = game.getLives()
|
||||
|
||||
|
@ -658,16 +699,19 @@ local function mainLoop(_M, genome)
|
|||
message(_M, "Extra live bonus added " .. extraLiveBonus)
|
||||
end
|
||||
|
||||
if game.getGoalHit() then
|
||||
local sprites = game.getSprites()
|
||||
-- FIXME We should test this before we time out
|
||||
if game.getGoalHit(sprites) then
|
||||
fitness = fitness + 1000
|
||||
message(_M, string.format("LEVEL WON! Fitness: %d", fitness), 0x0000ff00)
|
||||
end
|
||||
|
||||
if fitness == 0 then
|
||||
fitness = -1
|
||||
end
|
||||
genome.fitness = fitness
|
||||
|
||||
if fitness > _M.maxFitness then
|
||||
if _M.maxFitness == nil or fitness > _M.maxFitness then
|
||||
_M.maxFitness = fitness
|
||||
end
|
||||
|
||||
|
@ -693,7 +737,7 @@ local function mainLoop(_M, genome)
|
|||
input.keyhook("9", false)
|
||||
input.keyhook("tab", false)
|
||||
|
||||
return
|
||||
return _M.maxFitness
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -732,6 +776,18 @@ local function onLoad(_M, handler)
|
|||
table.insert(_M.onLoadHandler, handler)
|
||||
end
|
||||
|
||||
local function reset(_M)
|
||||
for i=#_M.onResetHandler,1,-1 do
|
||||
_M.onResetHandler[i]()
|
||||
end
|
||||
|
||||
message(_M, "Will be reset once all currently active threads finish", 0x00990000)
|
||||
end
|
||||
|
||||
local function onReset(_M, handler)
|
||||
table.insert(_M.onResetHandler, handler)
|
||||
end
|
||||
|
||||
local function keyhook (_M, key, state)
|
||||
if state.value == 1 then
|
||||
if key == "tab" then
|
||||
|
@ -753,9 +809,8 @@ local function keyhook (_M, key, state)
|
|||
_M.helddown = key
|
||||
load(_M)
|
||||
elseif key == "9" then
|
||||
-- FIXME Event inversion
|
||||
_M.helddown = key
|
||||
pool.run(true)
|
||||
reset(_M)
|
||||
end
|
||||
elseif state.value == 0 then
|
||||
_M.helddown = nil
|
||||
|
@ -827,6 +882,9 @@ local function saveLoadInput(_M)
|
|||
end
|
||||
|
||||
local function run(_M, species, generationIdx, genomeCallback)
|
||||
if beginRewindState == nil then
|
||||
beginRewindState = movie.to_rewind(config.NeatConfig.Filename)
|
||||
end
|
||||
game.registerHandlers()
|
||||
|
||||
_M.currentGenerationIndex = generationIdx
|
||||
|
@ -877,7 +935,7 @@ return function(promise)
|
|||
currentGenomeIndex = 1,
|
||||
currentFrame = 0,
|
||||
drawFrame = 0,
|
||||
maxFitness = 0,
|
||||
maxFitness = nil,
|
||||
|
||||
dereg = {},
|
||||
inputmode = false,
|
||||
|
@ -904,6 +962,7 @@ return function(promise)
|
|||
onMessageHandler = {},
|
||||
onSaveHandler = {},
|
||||
onLoadHandler = {},
|
||||
onResetHandler = {},
|
||||
onRenderFormHandler = {},
|
||||
}
|
||||
|
||||
|
@ -923,9 +982,13 @@ return function(promise)
|
|||
onLoad(_M, handler)
|
||||
end
|
||||
|
||||
_M.onReset = function(handler)
|
||||
onReset(_M, handler)
|
||||
end
|
||||
|
||||
_M.run = function(species, generationIdx, genomeCallback)
|
||||
return run(_M, species, generationIdx, genomeCallback)
|
||||
end
|
||||
|
||||
return _M
|
||||
end
|
||||
end
|
|
@ -80,6 +80,7 @@ _M.BadSprites = {
|
|||
klampon = 0x01f0,
|
||||
flotsam = 0x01f8,
|
||||
klinger = 0x0200,
|
||||
klingerSkidCloud = 0x0014,
|
||||
puftup = 0x0208,
|
||||
zingerAllColors = 0x0218,
|
||||
miniNecky = 0x0214,
|
||||
|
@ -105,13 +106,13 @@ end
|
|||
|
||||
function _M.InitSpriteList()
|
||||
for k,v in pairs(_M.GoodSprites) do
|
||||
_M.extSprites[v] = 1
|
||||
_M.Sprites[v] = 1
|
||||
end
|
||||
for k,v in pairs(_M.BadSprites) do
|
||||
_M.extSprites[v] = -1
|
||||
_M.Sprites[v] = -1
|
||||
end
|
||||
for k,v in pairs(_M.NeutralSprites) do
|
||||
_M.extSprites[v] = 0
|
||||
_M.Sprites[v] = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
local memory, movie, utime, callback, set_timer_timeout = memory, movie, utime, callback, set_timer_timeout
|
||||
local memory, movie, utime, callback, set_timer_timeout, input, gui, exec, settings = memory, movie, utime, callback, set_timer_timeout, input, gui, exec, settings
|
||||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
local Promise = dofile(base.."/promise.lua")
|
||||
|
@ -9,10 +9,11 @@ end)
|
|||
set_timer_timeout(1)
|
||||
local game = dofile(base.."/game.lua")(Promise)
|
||||
local util = dofile(base.."/util.lua")(Promise)
|
||||
local serpent = dofile(base.."/serpent.lua")
|
||||
|
||||
game.registerHandlers()
|
||||
game.getPositions()
|
||||
|
||||
game.findPreferredExit():next(function(exit)
|
||||
io.stderr:write(util.table_to_string(exit))
|
||||
io.stderr:write('\n')
|
||||
game.findPreferredExit():next(function(preferredExit)
|
||||
game.getWaypoints(preferredExit.x, preferredExit.y)
|
||||
end)
|
|
@ -1,20 +1,27 @@
|
|||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1").."/.."
|
||||
|
||||
local set_timer_timeout, memory, memory2, gui, input, bit = set_timer_timeout, memory, memory2, gui, input, bit
|
||||
local set_timer_timeout, memory, memory2, gui, input, bit, callback = set_timer_timeout, memory, memory2, gui, input, bit, callback
|
||||
|
||||
local warn = '========== The ROM must be running before running this script'
|
||||
io.stderr:write(warn)
|
||||
print(warn)
|
||||
|
||||
local util = dofile(base.."/util.lua")()
|
||||
local Promise = dofile(base.."/promise.lua")
|
||||
callback.register('timer', function()
|
||||
Promise.update()
|
||||
set_timer_timeout(1)
|
||||
end)
|
||||
set_timer_timeout(1)
|
||||
local util = dofile(base.."/util.lua")(Promise)
|
||||
local mem = dofile(base.."/mem.lua")
|
||||
local spritelist = dofile(base.."/spritelist.lua")
|
||||
local game = dofile(base.."/game.lua")()
|
||||
local config = dofile(base.."/config.lua")
|
||||
local game = dofile(base.."/game.lua")(Promise)
|
||||
|
||||
spritelist.InitSpriteList()
|
||||
spritelist.InitExtSpriteList()
|
||||
|
||||
game.registerHandlers()
|
||||
|
||||
local CAMERA_MODE = 0x7e054f
|
||||
local DIDDY_X_VELOCITY = 0x7e0e02
|
||||
local DIDDY_Y_VELOCITY = 0x7e0e06
|
||||
|
@ -130,44 +137,43 @@ function on_input (subframe)
|
|||
end
|
||||
end
|
||||
|
||||
local function get_sprite(base_addr)
|
||||
local function get_sprite(baseAddr)
|
||||
local spriteData = memory.readregion(baseAddr, mem.size.sprite)
|
||||
local offsets = mem.offset.sprite
|
||||
local cameraX = memory.readword(mem.addr.cameraX) - 256
|
||||
local cameraY = memory.readword(mem.addr.cameraY) - 256
|
||||
local x = memory.readword(base_addr + offsets.x)
|
||||
local y = memory.readword(base_addr + offsets.y)
|
||||
local x = util.regionToWord(spriteData, offsets.x)
|
||||
local y = util.regionToWord(spriteData, offsets.y)
|
||||
return {
|
||||
base_addr = string.format("%04x", base_addr),
|
||||
screenX = x - 256 - cameraX,
|
||||
screenY = y - 256 - cameraY - mem.size.tile / 3,
|
||||
control = memory.readword(base_addr + offsets.control),
|
||||
draworder = memory.readword(base_addr + 0x02),
|
||||
base_addr = string.format("%04x", baseAddr),
|
||||
screenX = x - game.cameraX,
|
||||
screenY = y - game.cameraY - mem.size.tile / 3,
|
||||
control = util.regionToWord(spriteData, offsets.control),
|
||||
draworder = util.regionToWord(spriteData, 0x02),
|
||||
x = x,
|
||||
y = y,
|
||||
jumpHeight = memory.readword(base_addr + offsets.jumpHeight),
|
||||
style = memory.readword(base_addr + offsets.style),
|
||||
currentframe = memory.readword(base_addr + 0x18),
|
||||
nextframe = memory.readword(base_addr + 0x1a),
|
||||
state = memory.readword(base_addr + 0x1e),
|
||||
velocityX = memory.readsword(base_addr + offsets.velocityX),
|
||||
velocityY = memory.readsword(base_addr + offsets.velocityY),
|
||||
velomaxx = memory.readsword(base_addr + 0x26),
|
||||
velomaxy = memory.readsword(base_addr + 0x2a),
|
||||
motion = memory.readword(base_addr + 0x2e),
|
||||
attr = memory.readword(base_addr + 0x30),
|
||||
animnum = memory.readword(base_addr + 0x36),
|
||||
remainingframe = memory.readword(base_addr + 0x38),
|
||||
animcontrol = memory.readword(base_addr + 0x3a),
|
||||
animreadpos = memory.readword(base_addr + 0x3c),
|
||||
animcontrol2 = memory.readword(base_addr + 0x3e),
|
||||
animformat = memory.readword(base_addr + 0x40),
|
||||
damage1 = memory.readword(base_addr + 0x44),
|
||||
damage2 = memory.readword(base_addr + 0x46),
|
||||
damage3 = memory.readword(base_addr + 0x48),
|
||||
damage4 = memory.readword(base_addr + 0x4a),
|
||||
damage5 = memory.readword(base_addr + 0x4c),
|
||||
damage6 = memory.readword(base_addr + 0x4e),
|
||||
spriteparam = memory.readword(base_addr + 0x58),
|
||||
jumpHeight = util.regionToWord(spriteData, offsets.jumpHeight),
|
||||
style = util.regionToWord(spriteData, offsets.style),
|
||||
currentframe = util.regionToWord(spriteData, 0x18),
|
||||
nextframe = util.regionToWord(spriteData, 0x1a),
|
||||
state = util.regionToWord(spriteData, 0x1e),
|
||||
velocityX = util.regionToSWord(spriteData, offsets.velocityX),
|
||||
velocityY = util.regionToSWord(spriteData, offsets.velocityY),
|
||||
velomaxx = util.regionToSWord(spriteData, 0x26),
|
||||
velomaxy = util.regionToSWord(spriteData, 0x2a),
|
||||
motion = util.regionToWord(spriteData, offsets.motion),
|
||||
attr = util.regionToWord(spriteData, 0x30),
|
||||
animnum = util.regionToWord(spriteData, 0x36),
|
||||
remainingframe = util.regionToWord(spriteData, 0x38),
|
||||
animcontrol = util.regionToWord(spriteData, 0x3a),
|
||||
animreadpos = util.regionToWord(spriteData, 0x3c),
|
||||
animcontrol2 = util.regionToWord(spriteData, 0x3e),
|
||||
animformat = util.regionToWord(spriteData, 0x40),
|
||||
damage1 = util.regionToWord(spriteData, 0x44),
|
||||
damage2 = util.regionToWord(spriteData, 0x46),
|
||||
damage3 = util.regionToWord(spriteData, 0x48),
|
||||
damage4 = util.regionToWord(spriteData, 0x4a),
|
||||
damage5 = util.regionToWord(spriteData, 0x4c),
|
||||
damage6 = util.regionToWord(spriteData, 0x4e),
|
||||
spriteparam = util.regionToWord(spriteData, 0x58),
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -202,6 +208,7 @@ local function sprite_details(idx)
|
|||
text(0, 0, "Sprite "..idx..(locked and " (Locked)" or "")..":\n\n"..util.table_to_string(sprite))
|
||||
end
|
||||
|
||||
local waypoints = {}
|
||||
local overlayCtx = nil
|
||||
local overlay = nil
|
||||
local function renderOverlay(guiWidth, guiHeight)
|
||||
|
@ -231,6 +238,8 @@ Sprite Details:
|
|||
return
|
||||
end
|
||||
|
||||
game.getPositions()
|
||||
|
||||
local toggles = ""
|
||||
|
||||
if pokemon then
|
||||
|
@ -254,17 +263,11 @@ Sprite Details:
|
|||
"Up"
|
||||
}
|
||||
|
||||
local cameraX = memory.readword(mem.addr.cameraX) - 256
|
||||
local cameraY = memory.readword(mem.addr.cameraY) - 256
|
||||
local cameraDir = memory.readbyte(CAMERA_MODE)
|
||||
|
||||
local direction = directions[cameraDir+1]
|
||||
|
||||
local vertical = memory.readword(mem.addr.tileCollisionMathPointer) == mem.addr.verticalPointer
|
||||
|
||||
local partyX = memory.readword(mem.addr.partyX)
|
||||
local partyY = memory.readword(mem.addr.partyY)
|
||||
local partyTileOffset = game.tileOffsetCalculation(partyX, partyY, vertical)
|
||||
local partyTileOffset = game.tileOffsetCalculation(game.partyX, game.partyY, game.vertical)
|
||||
|
||||
local stats = string.format([[
|
||||
%s camera %d,%d
|
||||
|
@ -273,11 +276,11 @@ Tile offset: %04x
|
|||
Main area: %04x
|
||||
Current area: %04x
|
||||
%s
|
||||
]], direction, cameraX, cameraY, vertical, partyTileOffset, memory.readword(mem.addr.mainAreaNumber), memory.readword(mem.addr.currentAreaNumber), util.table_to_string(game.getInputs()):gsub("[\\{\\},\n\"]", ""):gsub("-1", "X"):gsub("0", "."):gsub("1", "O"):gsub("(.............)", "%1\n"))
|
||||
]], direction, game.cameraX, game.cameraY, game.vertical, partyTileOffset, memory.readword(mem.addr.mainAreaNumber), memory.readword(mem.addr.currentAreaNumber), util.table_to_string(game.getInputs()):gsub("[\\{\\},\n\"]", ""):gsub("-1", "X"):gsub("0", "."):gsub("1", "O"):gsub("(.............)", "%1\n"))
|
||||
|
||||
text(guiWidth - 125, guiHeight - 200, stats)
|
||||
|
||||
text((partyX - 256 - cameraX) * 2, (partyY - 256 - cameraY) * 2 + 20, "Party")
|
||||
text((game.partyX - game.cameraX) * 2, (game.partyY - game.cameraY) * 2 + 20, "Party")
|
||||
|
||||
local sprites = {}
|
||||
for idx = 0,22,1 do
|
||||
|
@ -310,20 +313,45 @@ Current area: %04x
|
|||
::continue::
|
||||
end
|
||||
|
||||
if rulers and cameraX >= 0 then
|
||||
for i=1,#waypoints,1 do
|
||||
local screenX = (waypoints[i].x - game.cameraX) * 2
|
||||
local screenY = (waypoints[i].y - game.cameraY) * 2
|
||||
|
||||
if screenX > guiWidth - mem.size.tile * 2 then
|
||||
screenX = guiWidth - mem.size.tile * 2
|
||||
end
|
||||
|
||||
if screenY > guiHeight then
|
||||
screenY = guiHeight - 20
|
||||
end
|
||||
|
||||
if screenX < 0 then
|
||||
screenX = 0
|
||||
end
|
||||
|
||||
if screenY < 0 then
|
||||
screenY = 0
|
||||
end
|
||||
|
||||
text(screenX, screenY, "WAYPOINT "..i)
|
||||
end
|
||||
|
||||
text(guiWidth / 2, guiHeight - 20, "WAYPOINTS: "..#waypoints)
|
||||
|
||||
if rulers and game.cameraX >= 0 then
|
||||
local halfWidth = math.floor(guiWidth / 2)
|
||||
local halfHeight = math.floor(guiHeight / 2)
|
||||
|
||||
local cameraTileX = math.floor(cameraX / mem.size.tile)
|
||||
local cameraTileX = math.floor(game.cameraX / mem.size.tile)
|
||||
gui.line(0, halfHeight, guiWidth, halfHeight, BG_COLOR)
|
||||
for i = cameraTileX, cameraTileX + guiWidth / mem.size.tile / 2,1 do
|
||||
text((i * mem.size.tile - cameraX) * 2, halfHeight, tostring(i), FG_COLOR, BG_COLOR)
|
||||
text((i * mem.size.tile - game.cameraX) * 2, halfHeight, tostring(i), FG_COLOR, BG_COLOR)
|
||||
end
|
||||
|
||||
local cameraTileY = math.floor(cameraY / mem.size.tile)
|
||||
local cameraTileY = math.floor(game.cameraY / mem.size.tile)
|
||||
gui.line(halfWidth, 0, halfWidth, guiHeight, BG_COLOR)
|
||||
for i = cameraTileY, cameraTileY + guiHeight / mem.size.tile / 2,1 do
|
||||
text(halfWidth, (i * mem.size.tile - cameraY) * 2, tostring(i), FG_COLOR, BG_COLOR)
|
||||
text(halfWidth, (i * mem.size.tile - game.cameraY) * 2, tostring(i), FG_COLOR, BG_COLOR)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -331,10 +359,10 @@ Current area: %04x
|
|||
|
||||
for x = -TILE_RADIUS, TILE_RADIUS, 1 do
|
||||
for y = -TILE_RADIUS, TILE_RADIUS, 1 do
|
||||
local tileX = math.floor((partyX + x * mem.size.tile) / mem.size.tile) * mem.size.tile
|
||||
local tileY = math.floor((partyY + y * mem.size.tile) / mem.size.tile) * mem.size.tile
|
||||
local tileX = math.floor((game.partyX + x * mem.size.tile) / mem.size.tile) * mem.size.tile
|
||||
local tileY = math.floor((game.partyY + y * mem.size.tile) / mem.size.tile) * mem.size.tile
|
||||
|
||||
local offset = game.tileOffsetCalculation(tileX, tileY, vertical)
|
||||
local offset = game.tileOffsetCalculation(tileX, tileY, game.vertical)
|
||||
|
||||
local tile = memory.readword(tilePtr + offset)
|
||||
|
||||
|
@ -342,8 +370,8 @@ Current area: %04x
|
|||
goto continue
|
||||
end
|
||||
|
||||
local screenX = (tileX - 256 - cameraX) * 2
|
||||
local screenY = (tileY - 256 - cameraY) * 2
|
||||
local screenX = (tileX - 256 - game.cameraX) * 2
|
||||
local screenY = (tileY - 256 - game.cameraY) * 2
|
||||
if screenX < 0 or screenX > guiWidth or
|
||||
screenY < 0 or screenY > guiHeight then
|
||||
--goto continue
|
||||
|
@ -355,7 +383,7 @@ Current area: %04x
|
|||
end
|
||||
end
|
||||
|
||||
if cameraX >= 0 then
|
||||
if game.cameraX >= 0 then
|
||||
local oam = memory2.OAM:readregion(0x00, 0x220)
|
||||
|
||||
for idx=0,0x200/4-1,1 do
|
||||
|
@ -420,10 +448,6 @@ function on_paint (not_synth)
|
|||
end
|
||||
end
|
||||
|
||||
function on_timer()
|
||||
set_timer_timeout(100 * 1000)
|
||||
end
|
||||
|
||||
input.keyhook("1", true)
|
||||
input.keyhook("2", true)
|
||||
input.keyhook("3", true)
|
||||
|
@ -435,13 +459,11 @@ input.keyhook("8", true)
|
|||
input.keyhook("9", true)
|
||||
input.keyhook("0", true)
|
||||
|
||||
set_timer_timeout(100 * 1000)
|
||||
|
||||
for i=0,22,1 do
|
||||
memory2.BUS:registerwrite(mem.addr.spriteBase + mem.size.sprite * i + mem.offset.sprite.x, function(addr, val)
|
||||
print(memory.getregister('pc'))
|
||||
end)
|
||||
end
|
||||
game.findPreferredExit():next(function(preferredExit)
|
||||
return game.getWaypoints(preferredExit.x, preferredExit.y)
|
||||
end):next(function(w)
|
||||
waypoints = w
|
||||
end)
|
||||
|
||||
-- fe0a58 crate: near bunch and klomp on barrels
|
||||
-- fe0a58: Crate X position
|
||||
|
|
266
util.lua
266
util.lua
|
@ -1,4 +1,4 @@
|
|||
local utime, bit = utime, bit
|
||||
local utime, bit, callback, exec = utime, bit, callback, exec
|
||||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
|
@ -8,6 +8,143 @@ local _M = {}
|
|||
|
||||
_M.isWin = package.config:sub(1, 1) == '\\'
|
||||
|
||||
--- Converts a function into a promise.
|
||||
--- Useful for decoupling code from the original event it was fired in.
|
||||
---@param next function The function to resolve on the next tick
|
||||
---@return Promise Promise A promise that returns the value of the next function
|
||||
function _M.promiseWrap(next, value)
|
||||
local promise = Promise.new()
|
||||
promise:resolve(value)
|
||||
return promise:next(next)
|
||||
end
|
||||
|
||||
local times = {}
|
||||
|
||||
--- Track how long a function takes
|
||||
---@param toCall function Function that executes synchronously
|
||||
---@param name string What to print in the finish text
|
||||
---@return any any The original value
|
||||
function _M.time(toCall, name)
|
||||
if name == nil then
|
||||
name = 'function'
|
||||
end
|
||||
|
||||
local sec, usec = utime()
|
||||
local startTime = sec * 1000000 + usec
|
||||
local finishTime = 0
|
||||
local ret = toCall()
|
||||
sec, usec = utime()
|
||||
finishTime = sec * 1000000 + usec
|
||||
local t = times[name]
|
||||
if t == nil then
|
||||
t = {}
|
||||
times[name] = t
|
||||
end
|
||||
|
||||
if #t > 50 then
|
||||
table.remove(t, 1)
|
||||
end
|
||||
|
||||
t[#t+1] = finishTime - startTime
|
||||
local sum = 0
|
||||
for i=1,#t,1 do
|
||||
sum = sum + t[i]
|
||||
end
|
||||
|
||||
print(name..' is averaging '..math.floor(sum / #t))
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
--- Wait for a specified amount of time. Note that this is dependent on the
|
||||
--- timer timeout getting set elsewhere in the code, probably in the Promise
|
||||
--- handler setup
|
||||
---@param delayUsec number Number of microseconds to wait
|
||||
---@return Promise Promise A promise that resolves when the time has elapsed
|
||||
function _M.delay(delayUsec)
|
||||
return Promise.new(function(res, rej)
|
||||
local sec, usec = utime()
|
||||
local start = sec * 1000000 + usec
|
||||
local finish = start
|
||||
local unTimer = nil
|
||||
local function onTimer()
|
||||
sec, usec = utime()
|
||||
finish = sec * 1000000 + usec
|
||||
if finish - start >= delayUsec then
|
||||
callback.unregister('timer', unTimer)
|
||||
res()
|
||||
end
|
||||
end
|
||||
unTimer = callback.register('timer', onTimer)
|
||||
end)
|
||||
end
|
||||
|
||||
function _M.saveState(filename)
|
||||
return Promise.new(function(res, rej)
|
||||
local unSave = nil
|
||||
local unErrSave = nil
|
||||
local function errSave(f)
|
||||
if f == filename then
|
||||
callback.unregister('post_save', unSave)
|
||||
callback.unregister('err_save', unErrSave)
|
||||
rej()
|
||||
end
|
||||
end
|
||||
local function postSave(f)
|
||||
if f == filename then
|
||||
callback.unregister('post_save', unSave)
|
||||
callback.unregister('err_save', unErrSave)
|
||||
res()
|
||||
end
|
||||
end
|
||||
unSave = callback.register('post_save', postSave)
|
||||
unErrSave = callback.register('err_save', errSave)
|
||||
exec('save-state '..filename)
|
||||
end)
|
||||
end
|
||||
|
||||
function _M.loadAndStart(romFile)
|
||||
return Promise.new(function(res, rej)
|
||||
local unPaint = nil
|
||||
local paint = 0
|
||||
local function onPaint()
|
||||
paint = paint + 1
|
||||
|
||||
_M.promiseWrap(function()
|
||||
if paint == 1 then
|
||||
exec('pause-emulator')
|
||||
elseif paint > 1 then
|
||||
callback.unregister('paint', unPaint)
|
||||
res()
|
||||
end
|
||||
end)
|
||||
end
|
||||
unPaint = callback.register('paint', onPaint)
|
||||
|
||||
exec('load-rom '..romFile)
|
||||
end)
|
||||
end
|
||||
|
||||
function _M.getTempDir()
|
||||
local temps = {
|
||||
os.getenv("TMPDIR"),
|
||||
os.getenv("TEMP"),
|
||||
os.getenv("TEMPDIR"),
|
||||
os.getenv("TMP"),
|
||||
}
|
||||
|
||||
local tempDir = "/tmp"
|
||||
for i=1,#temps,1 do
|
||||
local temp = temps[i]
|
||||
if temp ~= nil and temp ~= "" then
|
||||
tempDir = temps[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return tempDir
|
||||
end
|
||||
|
||||
--- Echo a command, run it, and return the file handle
|
||||
--- @param cmd string The command to execute
|
||||
--- @param workdir string The working directory
|
||||
|
@ -48,6 +185,29 @@ function _M.doCmd(...)
|
|||
return _M.scrapeCmd('*a', ...)
|
||||
end
|
||||
|
||||
function _M.openReadPipe(name)
|
||||
if _M.isWin then
|
||||
local cmd = 'cd /d "'..base..'" && "'..base..'/namedpipe/createAndReadPipe.exe" "'..name..'"'
|
||||
print(cmd)
|
||||
return io.popen(cmd, 'r')
|
||||
else
|
||||
return io.popen("socat 'UNIX-LISTEN:".._M.getTempDir().."/"..name.."' -", 'r')
|
||||
end
|
||||
end
|
||||
|
||||
function _M.openReadPipeWriter(name)
|
||||
local writer = nil
|
||||
while writer == nil do
|
||||
if _M.isWin then
|
||||
writer = io.open('\\\\.\\pipe\\'..name, 'w')
|
||||
else
|
||||
writer = io.popen("socat 'UNIX-CONNECT:".._M.getTempDir().."/"..name.."' -", 'w')
|
||||
end
|
||||
end
|
||||
|
||||
return writer
|
||||
end
|
||||
|
||||
--- Download a url
|
||||
--- @param url string URI of resource to download
|
||||
--- @param dest string File to save resource to
|
||||
|
@ -59,9 +219,8 @@ end
|
|||
--- @param zipfile string The ZIP file path
|
||||
--- @param dest string Where to unzip the ZIP file. Beware ZIP bombs.
|
||||
function _M.unzip (zipfile, dest)
|
||||
return _M.doCmd('unzip "'..zipfile..'" -d "'..dest..
|
||||
'" 2>&1 || tar -C "'..dest..'" -xvf "'..zipfile..
|
||||
'" 2>&1', nil)
|
||||
return _M.doCmd('tar -xvf "'..zipfile..'" 2>&1 || unzip -n "'..zipfile..'" -d "'..dest..
|
||||
'" 2>&1', dest)
|
||||
end
|
||||
|
||||
--- Create a directory
|
||||
|
@ -100,92 +259,7 @@ function _M.closeCmd(handle)
|
|||
return
|
||||
end
|
||||
if code ~= 0 then
|
||||
error("The last command failed")
|
||||
end
|
||||
end
|
||||
|
||||
function _M.waitForFiles(filenames)
|
||||
if type(filenames) == 'string' then
|
||||
filenames = {filenames}
|
||||
end
|
||||
|
||||
local poppet = nil
|
||||
if _M.isWin then
|
||||
local sec, usec = utime()
|
||||
print(string.format('Starting watching file at %d', sec * 1000000 + usec))
|
||||
|
||||
local cmd = '"'..base..'/watchexec/watchexec.exe" "-w" "'..table.concat(filenames, '" "-w" "')..'" "echo" "%WATCHEXEC_WRITTEN_PATH%"'
|
||||
poppet = _M.popenCmd(cmd, base)
|
||||
|
||||
poppet:read("*l")
|
||||
|
||||
local waiters = {}
|
||||
for i=1,#filenames,1 do
|
||||
local waiter = Promise.new()
|
||||
table.insert(waiters, waiter)
|
||||
end
|
||||
|
||||
-- To defer the check of the files
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
promise:next(function()
|
||||
local i = 1
|
||||
while i <= filenames do
|
||||
local line = poppet:read("*l")
|
||||
for chr in line:gmatch(";") do
|
||||
i = i + 1
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
-- FIXME synchronous
|
||||
for i=1,#filenames,1 do
|
||||
waiters[i]:resolve(filenames[i])
|
||||
end
|
||||
end):catch(function(reason)
|
||||
for i=1,#filenames,1 do
|
||||
waiters[i]:reject(reason)
|
||||
end
|
||||
end)
|
||||
|
||||
return waiters
|
||||
else
|
||||
local watchCmd = [[bash ]]..base..[[/watch.sh ']]..table.concat(filenames, [[' ']])..[[']]
|
||||
poppet = _M.popenCmd(watchCmd)
|
||||
|
||||
local waiters = {}
|
||||
for i=1,#filenames,1 do
|
||||
local waiter = Promise.new()
|
||||
table.insert(waiters, waiter)
|
||||
end
|
||||
|
||||
local finished = 0
|
||||
local function waitLoop()
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
local line = poppet:read("*l")
|
||||
finished = finished + 1
|
||||
local filename = line:gsub('%s+[^%s]+$', "")
|
||||
for i=1,#filenames,1 do
|
||||
if filename == filenames[i] then
|
||||
waiters[i]:resolve(filenames[i])
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if finished ~= #filenames then
|
||||
return waitLoop()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
waitLoop():catch(function(reason)
|
||||
for i=1,#waiters,1 do
|
||||
waiters:reject(reason)
|
||||
end
|
||||
end)
|
||||
|
||||
return waiters
|
||||
error(string.format("The last command failed: %s %d", state, code))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -263,6 +337,18 @@ function _M.regionToWord(region, offset)
|
|||
return bit.compose(region[offset], region[offset + 1])
|
||||
end
|
||||
|
||||
function _M.regionToSWord(region, offset)
|
||||
local unsigned = _M.regionToWord(region, offset)
|
||||
local base = bit.band(unsigned, 0x7fff)
|
||||
local sign = bit.lrshift(unsigned, 15)
|
||||
-- There is a more mathematical way to do this but whatever
|
||||
if sign == 1 then
|
||||
return base-0x8000
|
||||
else
|
||||
return base
|
||||
end
|
||||
end
|
||||
|
||||
return function(promise)
|
||||
Promise = promise
|
||||
return _M
|
||||
|
|
21
watch.sh
21
watch.sh
|
@ -1,21 +0,0 @@
|
|||
#! /bin/bash
|
||||
FILENAMES=("$@")
|
||||
declare -A SEEN
|
||||
((I = 0))
|
||||
set -m
|
||||
which inotifywait >/dev/null
|
||||
function checker {
|
||||
inotifywait -q -m -e close_write "${FILENAMES[@]}" | while read LINE ; do
|
||||
if ! [ ${SEEN["$LINE"]+y} ] ; then
|
||||
SEEN["$LINE"]=1
|
||||
echo "$LINE"
|
||||
fi ;
|
||||
TOTAL=${#SEEN[@]}
|
||||
COUNT=$#
|
||||
if ((TOTAL == COUNT)) ; then
|
||||
kill -s TERM 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
checker &
|
||||
wait
|
193
zzlib.lua
Normal file
193
zzlib.lua
Normal file
|
@ -0,0 +1,193 @@
|
|||
-- zzlib - zlib decompression in Lua - Implementation-independent code
|
||||
|
||||
-- Copyright (c) 2016-2020 Francois Galea <fgalea at free.fr>
|
||||
-- This program is free software. It comes without any warranty, to
|
||||
-- the extent permitted by applicable law. You can redistribute it
|
||||
-- and/or modify it under the terms of the Do What The Fuck You Want
|
||||
-- To Public License, Version 2, as published by Sam Hocevar. See
|
||||
-- the COPYING file or http://www.wtfpl.net/ for more details.
|
||||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
local unpack = table.unpack or unpack
|
||||
local infl = dofile(base.."/inflate-bit32.lua")
|
||||
|
||||
local zzlib = {}
|
||||
|
||||
local function arraytostr(array)
|
||||
local tmp = {}
|
||||
local size = #array
|
||||
local pos = 1
|
||||
local imax = 1
|
||||
while size > 0 do
|
||||
local bsize = size>=2048 and 2048 or size
|
||||
local s = string.char(unpack(array,pos,pos+bsize-1))
|
||||
pos = pos + bsize
|
||||
size = size - bsize
|
||||
local i = 1
|
||||
while tmp[i] do
|
||||
s = tmp[i]..s
|
||||
tmp[i] = nil
|
||||
i = i + 1
|
||||
end
|
||||
if i > imax then
|
||||
imax = i
|
||||
end
|
||||
tmp[i] = s
|
||||
end
|
||||
local str = ""
|
||||
for i=1,imax do
|
||||
if tmp[i] then
|
||||
str = tmp[i]..str
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
local function inflate_gzip(bs)
|
||||
local id1,id2,cm,flg = bs.buf:byte(1,4)
|
||||
if id1 ~= 31 or id2 ~= 139 then
|
||||
error("invalid gzip header")
|
||||
end
|
||||
if cm ~= 8 then
|
||||
error("only deflate format is supported")
|
||||
end
|
||||
bs.pos=11
|
||||
if infl.band(flg,4) ~= 0 then
|
||||
local xl1,xl2 = bs.buf.byte(bs.pos,bs.pos+1)
|
||||
local xlen = xl2*256+xl1
|
||||
bs.pos = bs.pos+xlen+2
|
||||
end
|
||||
if infl.band(flg,8) ~= 0 then
|
||||
local pos = bs.buf:find("\0",bs.pos)
|
||||
bs.pos = pos+1
|
||||
end
|
||||
if infl.band(flg,16) ~= 0 then
|
||||
local pos = bs.buf:find("\0",bs.pos)
|
||||
bs.pos = pos+1
|
||||
end
|
||||
if infl.band(flg,2) ~= 0 then
|
||||
-- TODO: check header CRC16
|
||||
bs.pos = bs.pos+2
|
||||
end
|
||||
local result = arraytostr(infl.main(bs))
|
||||
local crc = bs:getb(8)+256*(bs:getb(8)+256*(bs:getb(8)+256*bs:getb(8)))
|
||||
bs:close()
|
||||
if crc ~= infl.crc32(result) then
|
||||
error("checksum verification failed")
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- compute Adler-32 checksum
|
||||
local function adler32(s)
|
||||
local s1 = 1
|
||||
local s2 = 0
|
||||
for i=1,#s do
|
||||
local c = s:byte(i)
|
||||
s1 = (s1+c)%65521
|
||||
s2 = (s2+s1)%65521
|
||||
end
|
||||
return s2*65536+s1
|
||||
end
|
||||
|
||||
local function inflate_zlib(bs)
|
||||
local cmf = bs.buf:byte(1)
|
||||
local flg = bs.buf:byte(2)
|
||||
if (cmf*256+flg)%31 ~= 0 then
|
||||
error("zlib header check bits are incorrect")
|
||||
end
|
||||
if infl.band(cmf,15) ~= 8 then
|
||||
error("only deflate format is supported")
|
||||
end
|
||||
if infl.rshift(cmf,4) ~= 7 then
|
||||
error("unsupported window size")
|
||||
end
|
||||
if infl.band(flg,32) ~= 0 then
|
||||
error("preset dictionary not implemented")
|
||||
end
|
||||
bs.pos=3
|
||||
local result = arraytostr(infl.main(bs))
|
||||
local adler = ((bs:getb(8)*256+bs:getb(8))*256+bs:getb(8))*256+bs:getb(8)
|
||||
bs:close()
|
||||
if adler ~= adler32(result) then
|
||||
error("checksum verification failed")
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function zzlib.gunzipf(filename)
|
||||
local file,err = io.open(filename,"rb")
|
||||
if not file then
|
||||
return nil,err
|
||||
end
|
||||
return inflate_gzip(infl.bitstream_init(file))
|
||||
end
|
||||
|
||||
function zzlib.gunzip(str)
|
||||
return inflate_gzip(infl.bitstream_init(str))
|
||||
end
|
||||
|
||||
function zzlib.inflate(str)
|
||||
return inflate_zlib(infl.bitstream_init(str))
|
||||
end
|
||||
|
||||
local function int2le(str,pos)
|
||||
local a,b = str:byte(pos,pos+1)
|
||||
return b*256+a
|
||||
end
|
||||
|
||||
local function int4le(str,pos)
|
||||
local a,b,c,d = str:byte(pos,pos+3)
|
||||
return ((d*256+c)*256+b)*256+a
|
||||
end
|
||||
|
||||
function zzlib.unzip(buf,filename)
|
||||
local p = #buf-21
|
||||
local quit = false
|
||||
if int4le(buf,p) ~= 0x06054b50 then
|
||||
-- not sure there is a reliable way to locate the end of central directory record
|
||||
-- if it has a variable sized comment field
|
||||
error(".ZIP file comments not supported")
|
||||
end
|
||||
local cdoffset = int4le(buf,p+16)
|
||||
local nfiles = int2le(buf,p+10)
|
||||
p = cdoffset+1
|
||||
for i=1,nfiles do
|
||||
if int4le(buf,p) ~= 0x02014b50 then
|
||||
error("invalid central directory header signature")
|
||||
end
|
||||
local flag = int2le(buf,p+8)
|
||||
local method = int2le(buf,p+10)
|
||||
local crc = int4le(buf,p+16)
|
||||
local namelen = int2le(buf,p+28)
|
||||
local name = buf:sub(p+46,p+45+namelen)
|
||||
if name == filename then
|
||||
local headoffset = int4le(buf,p+42)
|
||||
p = 1+headoffset
|
||||
if int4le(buf,p) ~= 0x04034b50 then
|
||||
error("invalid local header signature")
|
||||
end
|
||||
local csize = int4le(buf,p+18)
|
||||
local extlen = int2le(buf,p+28)
|
||||
p = p+30+namelen+extlen
|
||||
if method == 0 then
|
||||
-- no compression
|
||||
result = buf:sub(p,p+csize-1)
|
||||
else
|
||||
-- DEFLATE compression
|
||||
local bs = infl.bitstream_init(buf)
|
||||
bs.pos = p
|
||||
result = arraytostr(infl.main(bs))
|
||||
end
|
||||
if crc ~= infl.crc32(result) then
|
||||
error("checksum verification failed")
|
||||
end
|
||||
return result
|
||||
end
|
||||
p = p+46+namelen+int2le(buf,p+30)+int2le(buf,p+32)
|
||||
end
|
||||
error("file '"..filename.."' not found in ZIP archive")
|
||||
end
|
||||
|
||||
return zzlib
|
Loading…
Add table
Reference in a new issue