arflech
He/Him
Joined: 5/3/2008
Posts: 1120
partyboy1a wrote:
p-solver-v1.0.1, fixed a crash which could happen if the very first attempt was faster than all step-limited attempts afterwards.
That Mediafire link doesn't work for me: I've gone through 4 "Try again?" rounds when trying to download the file.
i imgur com/QiCaaH8 png
Joined: 3/14/2008
Posts: 152
Location: United Kingdom
arflech wrote:
partyboy1a wrote:
p-solver-v1.0.1, fixed a crash which could happen if the very first attempt was faster than all step-limited attempts afterwards.
That Mediafire link doesn't work for me: I've gone through 4 "Try again?" rounds when trying to download the file.
Mirror here: http://duk.im/p-solver-v1.0.1.zip if you still need it. I haven't forgotten about this, my last exam is tomorrow so I will finally get round to spending some time identifying the cause/requirements of the 6 frame 2 touch glitch and continuing with the Tas. Also I'm a fair bit better at programming now than in November when I started looking at this so I'll have a look at scripting automated input to speed up tasing.
Player (136)
Joined: 9/18/2007
Posts: 389
Well, it seems that we did a bad job in finding the rules for valid input. I just made a small test which demonstrates faster input methods: http://tasvideos.org/userfiles/info/8602286225201954 Too bad this will make it necessary to rewrite the solver.
Joined: 3/14/2008
Posts: 152
Location: United Kingdom
Interesting, so you can move two blocks per frame without delaying. By the looks of it this will be faster than or the same speed as tapping (3 frames on square at other end) in almost all cases, but if you had to go the full width (of your example grid) it would be one frame faster to tap. I'm surprised I never found this, though it's also possible it's a new thing only in desmume 0.99 (if that's what you're using? I just downloaded 0.9.9 to watch your test, most of the other testing I did was with 0.9.8 official and 'improved' versions by gocha of 0.9.7 and 0.9.8). Are the rules more complex than this, because it seems the "3 frame rule" where any touch of 2 or 1 frame would not register?
Player (136)
Joined: 9/18/2007
Posts: 389
The rules are just slightly more complex: 3 frames required for first tile. 1 frame if you just jump 1 or 2 tiles if you want to jump a wider distance (or to finish the path) - click the tile you want to jump from for 2 frames - one frame with no input - click your destination for 3 frames. So you need at least 3 + 2 + 1 + 3 = 9 frames to solve a puzzle. For puzzle mode, the new strategy is always better, it never makes sense to use a wide jump. For almost all cases and a board of at most 11x11 tiles (including the border), the new strategy is at least as fast as the old one. If we name the tiles like in a chess board and just write down which tile to click each frame:
Old: ..... A1, A1, A1, nil, A11, A11, A11
New: ..... A1,[A1, A1,] A3, A5,   A7, A9, A11{, A11}
[] required if the path starts at A1, {} required if the path finishes at A11. Note that the last input from the 1-frame sequence is ignored. For example: A1, A1, A1, C1, E1, F1, nil, nil will let the path finish at E1. I already adjusted the solver for the new strategy, will be posted when both strategies can be used.
Joined: 3/14/2008
Posts: 152
Location: United Kingdom
EDIT: I just did some testing and it's in fact possible to jump 3 squares in one frame - you have click at the close edges though, so that the distance is just over 2 full squares long (I'm guessing a set number of pixels) and you can only repeatedly go 3 sqaures if you change direction every time, otherwise you have to do 3-2-3 etc. Though it shouldn't overcomplicate the rules too much as if you plan ahead you should be able to position your clicks to allow you to do this. I haven't tested if it works with diagonals yet though. I'll upload a demo soon. Edit 2: dsm here: http://tasvideos.org/userfiles/info/8726755826533573 Video: Link to video I did some testing with my old emulator versions and found something very odd - I have 3 versions of desmume 0.9.8, two seemingly being the official version (and same compile date) and one build by gocha (r4450). The new 2 square moving thing seems to work on all of them, however your demo doesn't sync on one of the official ones so I must have some differences in the configuration (though I couldn't find any differences apart from input hotkeys so I really don't know. I guess it's the fact that you need extra frames of ignored input after the 2 frames that I didn't find it, however neither did Dassan back when he tried on the first page. Anyway I think if we want to get the puzzle mode done we should choose a standard version - the official 0.9.9 or if it becomes available, a version which fixes the lua emu.frameadvance bug (though there is a sort of workaround if you only want to use it to run a bot: emu.frameadvance is partially implemented, however it only works when the emulator is in a non paused state, so you can't use it while frameadvancing, but a fully autonomous bot would be able to use it if you never paused the emulator). You can see the code for it here: http://sourceforge.net/p/desmume/code/4722/tree/trunk/desmume/src/lua-engine.cpp#l1704 where the wrong function is called if the emulator is paused. The missing code is in this strange #define 'function' http://sourceforge.net/p/desmume/code/4722/tree/trunk/desmume/src/lua-engine.cpp#l1560 . Though without understanding the whole codebase I'm not 100% that this is the right place. It seems development is active on desmume as a whole though, with the last commit to that repository 2 days ago (lua engine unchanged for 8 months) so I'll try and post a bug on whatever issue tracker they have. Specifying paths simply as the "chess" co-ordinates seems like a good idea and would make it much easier to read the input with a bot.
Player (136)
Joined: 9/18/2007
Posts: 389
OK, so it seems to be possible to move the cursor by 40 pixels per frame without delaying. All tiles are 16x16 pixels, so speed can be at most 2.5 tiles per frame. I adjusted the solver one more time, now it has a really nice way for Lua interoperation: p-solver-v3.0.0.zip. You only need to find out the coordinates of the topmost leftmost tile and adjust the coordinates accordingly.
Joined: 3/14/2008
Posts: 152
Location: United Kingdom
Thanks for the solver, I made a lua script that can be used to solve and input a puzzle, though I guess you might have already made your own. The lua table output was very nice since you can just 'dofile()' it =). Script here (needs cleaning up/comments but it shouldn't be too hard to follow):
Language: lua

MEM_COLS = 0x020E3F64 MEM_ROWS = 0x020E3FA8 MEM_CELL_ROW1_COL1 = 0x020E03C4 MEM_rowoffset = 0x0140 MEM_coloffset = 0x14 MEM_valoffset = 0x1 frame = movie.framecount() require("queue") queue = Queue.new() function inputsetup() if memory.readbyte(0x020C8D3C) == 13 then xo = memory.readbyte(0x020E3F48) yo = memory.readbyte(0x020E3F4A) else xo = 40 yo = 0 end dofile("luabot_fastest_puzzle") for i,v in ipairs(SolutionPath) do Queue.push(queue, v) end end function readpuzzle() thestring = "" puzzle = {} rows = 9 --for challenge mode --check if we are in puzzle mode if memory.readbyte(0x020C8D3C) == 13 then rows = memory.readbyte(MEM_ROWS) end cols = memory.readbyte(MEM_COLS) gui.text(1,1,stylus.get()) gui.text(1,11, input.get()) for r = rows, 1, -1 do puzzle[r] = {} for c = 1, cols do puzzle[r][c] = memory.readbyte(MEM_CELL_ROW1_COL1 + MEM_rowoffset * (r - 1) + MEM_coloffset * (c - 1)) - MEM_valoffset thestring = thestring .. puzzle[r][c] .. " " end thestring = thestring .."\n" end gui.text(0,20,frame) return puzzle end function solvepuzzle() local keys= input.get() for k,v in pairs(keys) do if k=="left" then os.remove("puzzle") file = io.open("puzzle","w") file:write(rows .. " " .. cols .. "\n") print(file:write(thestring)) file:flush() end if k=="down" then os.execute("p-solver.exe puzzle") inputsetup() end end frame=frame+1 inputtable = {} if not Queue.empty(queue) then inputtable = Queue.pop(queue) if next(inputtable) then inputtable.x = inputtable.x + xo inputtable.y = inputtable.y + yo inputtable.touch = true --print(inputtable.x .. " " .. inputtable.y) end end stylus.set(inputtable) end gui.register(readpuzzle) emu.registerbefore(solvepuzzle) function file_exists(name) local f=io.open(name,"r") if f~=nil then io.close(f) return true else return false end end function readsol() file2 = io.open("fastest_puzzle", "r") count = 0 while true do local line = file2:read() if (line == nil) then break end end end
Also requires http://pastebin.com/djQWrKWY To use it press left then down when on a puzzle and it solves it. 0x20E3F48 = X offset of grid 0x20E3F4A = Y offset Both also work in challenge mode, though the Y offset is a negative value corresponding to the top screen so I just hardcoded them both in if it's challenge mode. I tested it on all 100 puzzles and it worked for all, and it also works for challenge mode, though when you get to level 2 and beyond, letting the blocks stack up can result in unsolvable puzzles. Video: Link to video The unsolvables start at 4:48, though on the first one just by chance doing the wrong input (the previous one as the table wasn't updated and the old input was read into the queue) happens to make it solvable. I don't know how hard it would be to calculate the required input though, or if it would even be faster than just luck manipulating for a solvable puzzle or doing it in smaller pieces. If I can find the positions of the level select buttons in memory then the whole of the puzzle mode (without any frames for "drawing") TAS could be generated by a lua script, which I think is pretty cool. I did some checks and I'm fairly sure the required delays are the same each time though I only tested 4 puzzles (also these may be off by 1 depending on how you think of the input):
  • Last frame of input on "tick" to select puzzle -> First frame of input possible on puzzle = 36 frames
  • Last frame of input on puzzle -> First frame you can press start with the puzzle counting as completed = 115 frames (the time where you can draw whatever you want)
  • Start press -> First frame of input on puzzle select screen = 58 frames
Editor, Player (44)
Joined: 7/11/2010
Posts: 1022
If you do a challenge mode puzzle in two or more strokes, it's likely to be fastest if the last stroke is the only one that clears lines. The length of time it takes for the line-clear animation is really slow compared to just about anything else. I'm not sure how fast a two-stroke solution is compared to a one-stroke solution. I can certainly believe that for pathological puzzles, it could be faster. However, I also suspect it's likely that it's never faster in practice except when a one-stroke solution is impossible.
Player (136)
Joined: 9/18/2007
Posts: 389
There are some functions missing for challenge mode right now. The solver will only work correctly if there is either 1 or 0 empty lines on the top part of the puzzle. And if there is no empty line on the top, you need the --no_top_border option, tell the solver about all 10 rows, and ignore the top row of the solution. The solver uses a 10x9 field with 1 tile of border on all sides internally, but then forbids using any field of the top row. If there are two or more empty lines, this won't be handled correctly right now. The solver will use at most one line from the top border. And: It's a pity your assumption that the input between the puzzles can be botted entirely is wrong. Up from the fifth puzzle on, 4 more waiting frames are required, and some even need 8 waiting frames. I tested it up to the first 50 puzzles. Pressing start too early will result in the puzzle counting as not being solved... HERE is a Lua script which generates a movie file which solves the first 50 puzzles. It's somewhat optimized, but some fine-tuning can still be done. (You can skip the first ~370 lines, they contain the TASed start before the first puzzle.) It requires luabot_fastest_puzzle_xxx for all 100 puzzles to be in the same directory. -------------- For strategy: We should test if it's faster to let the blocks stack up so high that it almost triggers a game over before we start to delete any rows. This would decrease the falling distance for the topmost tiles by quite a large amount.
Player (136)
Joined: 9/18/2007
Posts: 389
p-solver-v3.1.0 now has a --challenge_mode option. There might exist puzzles in challenge mode that could not be solved beforehand. The following puzzle is unsolvable in normal mode, but is solvable in challenge mode thanks to 2 empty lines on the top:
8 17
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 1 1 1 1 0 1 1 1 0 1 1 1 1 1 0
0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0
1 1 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0
0 1 0 0 0 1 0 1 0 0 0 0 1 1 1 0 0
1 1 0 0 0 1 0 1 0 0 0 0 1 0 1 0 0
0 0 0 0 0 1 0 1 0 0 0 0 1 0 1 0 0
(It will also display a warning that the generated solution is possibly suboptimal for this one, but the game doesn't support that large puzzles. I'm not going to implement the mixed strategy for that reason.)
Player (136)
Joined: 9/18/2007
Posts: 389
The tools evolve. This was once a program of about 2000 lines of code. Now, thanks to new tools from "Answer Set Programming", the essential part of the solver can be written with just 38 lines of code ( + a few lines of input for the actual puzzle). The following code, written for Clingo, will solve an example puzzle. It is also not that hard to read: if the line doesn't contain :- then it's a fact. if the line starts with :- then it says that the part on the right must not be true. a comma is a conjunction, so "a, not b" says "a must be true and b must not be true". { ... } is a set. if a fact only consists of a set, then the solver may freely choose which of the variables in the set are true (so that all other conditions are satisfied). m { ... } n says this set must consist of at least m and at most n members. so {a,b,c}2 says that at most 2 of a,b, and c must be true if a line has a structure "something_on_the_left" :- "something_on_the_right" then it means that if "something_on_the_right" is true, then the program will make sure that "something_on_the_left" is also true. The biggest difference: instead of a huge "visited" structure, 6 lines of code about which tiles were "reached" is enough, so the amount of variables is greatly reduced. also: the formalization of the rules is completely independant of the actual size of the puzzle (exactly one line of code needs to be changed for a different number of rows, in the most obvious way). Just leaving it here, should someone decide that it is worth the effort to adjust the new formalization in such a way that it can be used for the other mode of the game. I also have a version three times as long, just for comments.
tile(0..rows+1,0..cols+1).
puzzle(1..rows,1..cols).
{ use_black(1..rows) }.
{ use_tile(0..rows+1, (0;cols+1)) }.
{ use_tile((0;rows+1), 0..cols+1) }.
{ black(X,Y); white(X,Y) } = 1 :- puzzle(X,Y).
{ start(1..rows,1..cols) } = 1.
{ end(1..rows,1..cols) } = 1.
{ start(X,Y); end(X,Y) } < 2 :- puzzle(X,Y).
use_tile(X,Y) :- use_black(X), black(X,Y).
use_tile(X,Y) :- not use_black(X), white(X,Y).
{ h(X,Y) : tile(X,Y), tile(X,Y+1) }.
{ v(X,Y) : tile(X,Y), tile(X+1,Y) }.
:- tile(X,Y), use_tile(X,Y),
    { start(X,Y); end(X,Y); v(X-1,Y); v(X,Y); h(X,Y-1); h(X,Y) } != 2.
:- tile(X,Y), not use_tile(X,Y),
    { start(X,Y); end(X,Y); v(X-1,Y); v(X,Y); h(X,Y-1); h(X,Y) } != 0.
reached(X,Y) :- start(X,Y).
reached(X,Y+1) :- reached(X,Y), h(X,Y).
reached(X,Y-1) :- reached(X,Y), h(X,Y-1).
reached(X-1,Y) :- reached(X,Y), v(X-1,Y).
reached(X+1,Y) :- reached(X,Y), v(X,Y).
:- use_tile(X,Y), not reached(X,Y).
#show end/2.
#show touch/2.
#show h/2.
#show v/2.
#show start/2.
{ touch(X,Y) : tile(X,Y) }.
:- touch(X,Y), not use_tile(X,Y).
{ u(X,Y) : touch(X,Y) }.
{ l(X,Y) : touch(X,Y) }.
:- 1 { h(X,Y-1); h(X,Y) } 1, not touch(X,Y), tile(X,Y).
:- 1 { v(X-1,Y); v(X,Y) } 1, not touch(X,Y), tile(X,Y).
:- tile(X,Y), h(X,Y), not touch(X,Y), not touch(X,Y+1),
    {h(X,Y-1); h(X,Y+1); touch(X,Y-1); touch(X,Y+2); not l(X,Y-1); l(X,Y+2)} < 6.
:- tile(X,Y), v(X,Y), not touch(X,Y), not touch(X+1,Y),
    {v(X-1,Y); v(X+1,Y); touch(X-1,Y); touch(X+2,Y); not u(X-1,Y); u(X+2,Y)} < 6.
#const rows=5.
#const cols=9.
black(1..rows,1).
black((1;5),2).
white(2..4,2).
black((1;3;5),3).
white((2;4),3).
black((1;5),4).
white(2..4,4).
black(1..rows,5).
black((1;5),6).
white(2..4,6).
black((1;3;5),7).
white((2;4),7).
black((1;5),8).
white(2..4,8).
black(1..rows,9).
#minimize { 1,X,Y : touch(X,Y) }.