Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
E
exapunks-hackmatch-bot
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Taddeüs Kroes
exapunks-hackmatch-bot
Commits
d0515a68
Commit
d0515a68
authored
Apr 11, 2020
by
Taddeüs Kroes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Dynamic programming: reuse changes made by common prefix moves
parent
409354ab
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
170 additions
and
89 deletions
+170
-89
bot.py
bot.py
+15
-8
strategy.py
strategy.py
+155
-81
No files found.
bot.py
View file @
d0515a68
...
@@ -56,10 +56,8 @@ if __name__ == '__main__':
...
@@ -56,10 +56,8 @@ if __name__ == '__main__':
vprint_state
(
state
)
vprint_state
(
state
)
vprint
()
vprint
()
start
=
time
.
time
()
assert
state
.
exa
is
not
None
newstate
=
state
.
solve
()
end
=
time
.
time
()
vprint
(
'thought for'
,
round
((
end
-
start
)
*
1000
,
1
),
'ms'
)
except
(
TypeError
,
AssertionError
):
except
(
TypeError
,
AssertionError
):
vprint
(
'
\
r
error during parsing, wait for a bit...'
,
end
=
''
)
vprint
(
'
\
r
error during parsing, wait for a bit...'
,
end
=
''
)
time
.
sleep
(
0.050
)
time
.
sleep
(
0.050
)
...
@@ -69,14 +67,23 @@ if __name__ == '__main__':
...
@@ -69,14 +67,23 @@ if __name__ == '__main__':
time
.
sleep
(
0.500
)
time
.
sleep
(
0.500
)
continue
continue
try
:
start
=
time
.
time
()
newstate
=
state
.
solve
()
end
=
time
.
time
()
vprint
(
'thought for'
,
round
((
end
-
start
)
*
1000
,
1
),
'ms'
)
except
AssertionError
:
print
(
'error board 99:'
)
state
.
print
()
board
.
convert
(
'RGB'
).
save
(
'screens/board99.png'
)
break
if
state
.
held
==
NOBLOCK
and
any
(
map
(
newstate
.
loops
,
buf
)):
if
state
.
held
==
NOBLOCK
and
any
(
map
(
newstate
.
loops
,
buf
)):
vprint
(
'
\
r
loop detected, wait for a bit...'
,
end
=
''
)
vprint
(
'
\
r
loop detected, wait for a bit...'
,
end
=
''
)
time
.
sleep
(
0.03
)
time
.
sleep
(
0.03
)
elif
newstate
.
moves
:
elif
newstate
.
moves
:
vprint
(
'moves:'
,
newstate
.
keys
())
vprint
(
'moves:'
,
newstate
.
keys
())
vprint
(
' score:'
,
newstate
.
score
)
vprint
(
'score:'
,
newstate
.
score
)
if
buf
:
vprint
(
'prev score:'
,
buf
[
-
1
].
score
)
vprint
()
vprint
()
vprint
(
'target after moves:'
)
vprint
(
'target after moves:'
)
...
@@ -88,7 +95,7 @@ if __name__ == '__main__':
...
@@ -88,7 +95,7 @@ if __name__ == '__main__':
#moves_delay = max(0, newstate.delay() - keys_delay)
#moves_delay = max(0, newstate.delay() - keys_delay)
#vprint('wait for', moves_delay, 'ms')
#vprint('wait for', moves_delay, 'ms')
#time.sleep(moves_delay / 1000)
#time.sleep(moves_delay / 1000)
time
.
sleep
(
0.0
75
)
time
.
sleep
(
0.0
80
)
elif
state
.
nrows
-
2
<=
MAX_SPEED_ROWS
:
elif
state
.
nrows
-
2
<=
MAX_SPEED_ROWS
:
vprint
(
'no moves, speed up'
)
vprint
(
'no moves, speed up'
)
press_keys
(
win
,
'l'
)
press_keys
(
win
,
'l'
)
...
...
strategy.py
View file @
d0515a68
...
@@ -2,6 +2,7 @@ import io
...
@@ -2,6 +2,7 @@ import io
import
time
import
time
from
collections
import
deque
from
collections
import
deque
from
contextlib
import
redirect_stdout
from
contextlib
import
redirect_stdout
from
copy
import
copy
from
itertools
import
combinations
,
islice
from
itertools
import
combinations
,
islice
from
detection
import
COLUMNS
,
NOBLOCK
,
detect_blocks
,
detect_exa
,
\
from
detection
import
COLUMNS
,
NOBLOCK
,
detect_blocks
,
detect_exa
,
\
detect_held
,
print_board
,
is_basic
,
is_bomb
detect_held
,
print_board
,
is_basic
,
is_bomb
...
@@ -17,8 +18,6 @@ MOVE_DELAYS = (
...
@@ -17,8 +18,6 @@ MOVE_DELAYS = (
30
,
# RIGHT
30
,
# RIGHT
30
,
# SPEED
30
,
# SPEED
)
)
GET
=
((
GRAB
,),
(
SWAP
,
GRAB
),
(
GRAB
,
SWAP
,
DROP
,
SWAP
,
GRAB
))
PUT
=
((
DROP
,),
(
DROP
,
SWAP
),
(
DROP
,
SWAP
,
GRAB
,
SWAP
,
DROP
))
MIN_BASIC_GROUP_SIZE
=
4
MIN_BASIC_GROUP_SIZE
=
4
MIN_BOMB_GROUP_SIZE
=
2
MIN_BOMB_GROUP_SIZE
=
2
POINTS_DEPTH
=
3
POINTS_DEPTH
=
3
...
@@ -31,25 +30,39 @@ BOMB_POINTS = 5
...
@@ -31,25 +30,39 @@ BOMB_POINTS = 5
class
State
:
class
State
:
def
__init__
(
self
,
blocks
,
exa
,
held
,
colskip
=
None
):
def
__init__
(
self
,
blocks
,
exa
,
held
,
colskip
,
busy
,
moves
,
placed
,
grabbed
):
self
.
blocks
=
blocks
self
.
blocks
=
blocks
self
.
exa
=
exa
self
.
exa
=
exa
self
.
held
=
held
self
.
held
=
held
self
.
moves
=
()
self
.
score
=
()
self
.
nrows
=
len
(
self
.
blocks
)
//
COLUMNS
if
colskip
is
None
:
colskip
=
[]
for
col
in
range
(
COLUMNS
):
for
row
in
range
(
self
.
nrows
):
if
self
.
blocks
[
row
*
COLUMNS
+
col
]
!=
NOBLOCK
:
colskip
.
append
(
row
)
break
else
:
colskip
.
append
(
self
.
nrows
)
self
.
colskip
=
colskip
self
.
colskip
=
colskip
self
.
busy
=
busy
self
.
moves
=
moves
self
.
placed
=
placed
self
.
grabbed
=
grabbed
self
.
nrows
=
len
(
blocks
)
//
COLUMNS
@
classmethod
def
detect
(
cls
,
board
,
pad
=
2
):
blocks
=
[
NOBLOCK
]
*
(
COLUMNS
*
pad
)
+
list
(
detect_blocks
(
board
))
exa
=
detect_exa
(
board
)
held
=
detect_held
(
board
,
exa
)
colskip
=
get_colskip
(
blocks
)
busy
=
get_busy
(
blocks
,
colskip
)
return
cls
(
blocks
,
exa
,
held
,
colskip
,
busy
,
(),
set
(),
{})
def
copy
(
self
,
deep
):
mcopy
=
copy
if
deep
else
lambda
x
:
x
return
self
.
__class__
(
mcopy
(
self
.
blocks
),
self
.
exa
,
self
.
held
,
mcopy
(
self
.
colskip
),
self
.
busy
,
self
.
moves
,
mcopy
(
self
.
placed
),
mcopy
(
self
.
grabbed
))
def
colbusy
(
self
,
col
):
return
(
self
.
busy
>>
col
)
&
1
def
grabbing_or_dropping
(
self
):
def
grabbing_or_dropping
(
self
):
skip
=
self
.
colskip
[
self
.
exa
]
skip
=
self
.
colskip
[
self
.
exa
]
...
@@ -66,17 +79,6 @@ class State:
...
@@ -66,17 +79,6 @@ class State:
for
col
in
range
(
COLUMNS
):
for
col
in
range
(
COLUMNS
):
yield
gen_col
(
col
)
yield
gen_col
(
col
)
@
classmethod
def
detect
(
cls
,
board
,
pad
=
2
):
blocks
=
[
NOBLOCK
]
*
(
COLUMNS
*
pad
)
+
list
(
detect_blocks
(
board
))
exa
=
detect_exa
(
board
)
held
=
detect_held
(
board
,
exa
)
return
cls
(
blocks
,
exa
,
held
)
def
copy
(
self
):
return
self
.
__class__
(
list
(
self
.
blocks
),
self
.
exa
,
self
.
held
,
list
(
self
.
colskip
))
def
causes_panic
(
self
):
def
causes_panic
(
self
):
return
self
.
max_colsize
()
>=
COLSIZE_PANIC
return
self
.
max_colsize
()
>=
COLSIZE_PANIC
...
@@ -99,38 +101,48 @@ class State:
...
@@ -99,38 +101,48 @@ class State:
score
+=
row
-
start_row
+
1
score
+=
row
-
start_row
+
1
return
score
return
score
def
move
(
self
,
moves
):
def
colrows
(
self
,
col
):
s
=
self
.
copy
()
if
moves
else
self
return
self
.
nrows
-
self
.
colskip
[
col
]
s
.
moves
=
moves
s
.
placed
=
set
()
def
move
(
self
,
*
moves
):
s
.
grabbed
=
{}
deep
=
any
(
move
in
(
GRAB
,
DROP
,
SWAP
)
for
move
in
moves
)
s
=
self
.
copy
(
deep
)
s
.
moves
+=
moves
for
move
in
moves
:
for
move
in
moves
:
if
move
==
LEFT
:
if
move
==
LEFT
:
assert
s
.
exa
>
0
assert
s
.
exa
>
0
s
.
exa
-=
1
s
.
exa
-=
1
elif
move
==
RIGHT
:
elif
move
==
RIGHT
:
assert
s
.
exa
<
COLUMNS
-
1
assert
s
.
exa
<
COLUMNS
-
1
s
.
exa
+=
1
s
.
exa
+=
1
elif
move
==
GRAB
:
elif
move
==
GRAB
:
assert
not
s
.
colbusy
(
s
.
exa
)
assert
s
.
held
==
NOBLOCK
assert
s
.
held
==
NOBLOCK
row
=
s
.
colskip
[
s
.
exa
]
row
=
s
.
colskip
[
s
.
exa
]
assert
row
<
s
.
nrows
assert
row
<
s
.
nrows
i
=
row
*
COLUMNS
+
s
.
exa
i
=
row
*
COLUMNS
+
s
.
exa
s
.
held
=
s
.
blocks
[
i
]
s
.
grabbed
[
i
]
=
s
.
held
=
s
.
blocks
[
i
]
s
.
blocks
[
i
]
=
NOBLOCK
s
.
blocks
[
i
]
=
NOBLOCK
s
.
grabbed
[
i
]
=
s
.
held
s
.
grabbed
[
i
]
=
s
.
held
s
.
colskip
[
s
.
exa
]
+=
1
s
.
colskip
[
s
.
exa
]
+=
1
elif
move
==
DROP
:
elif
move
==
DROP
:
assert
not
s
.
colbusy
(
s
.
exa
)
assert
s
.
held
!=
NOBLOCK
assert
s
.
held
!=
NOBLOCK
row
=
s
.
colskip
[
s
.
exa
]
row
=
s
.
colskip
[
s
.
exa
]
assert
row
>
0
assert
row
>
0
# XXX assert s.nrows - row < COLSIZE_MAX
i
=
(
row
-
1
)
*
COLUMNS
+
s
.
exa
i
=
(
row
-
1
)
*
COLUMNS
+
s
.
exa
s
.
blocks
[
i
]
=
s
.
held
s
.
blocks
[
i
]
=
s
.
held
s
.
held
=
NOBLOCK
s
.
held
=
NOBLOCK
s
.
placed
.
add
(
i
)
s
.
placed
.
add
(
i
)
s
.
colskip
[
s
.
exa
]
-=
1
s
.
colskip
[
s
.
exa
]
-=
1
elif
move
==
SWAP
:
elif
move
==
SWAP
:
assert
not
s
.
colbusy
(
s
.
exa
)
row
=
s
.
colskip
[
s
.
exa
]
row
=
s
.
colskip
[
s
.
exa
]
i
=
row
*
COLUMNS
+
s
.
exa
i
=
row
*
COLUMNS
+
s
.
exa
j
=
i
+
COLUMNS
j
=
i
+
COLUMNS
...
@@ -145,9 +157,6 @@ class State:
...
@@ -145,9 +157,6 @@ class State:
s
.
placed
.
add
(
i
)
s
.
placed
.
add
(
i
)
s
.
placed
.
add
(
j
)
s
.
placed
.
add
(
j
)
if
moves
and
self
.
max_colsize
()
<
COLSIZE_MAX
:
assert
s
.
max_colsize
()
<=
COLSIZE_MAX
return
s
return
s
def
find_groups
(
self
,
depth
=
POINTS_DEPTH
,
minsize
=
2
):
def
find_groups
(
self
,
depth
=
POINTS_DEPTH
,
minsize
=
2
):
...
@@ -247,66 +256,110 @@ class State:
...
@@ -247,66 +256,110 @@ class State:
return
-
points
return
-
points
def
gen_moves
(
self
):
def
gen_moves
(
self
):
yield
()
yield
self
def
shift_exa
(
diff
):
for
src
in
self
.
gen_shift
(
not
self
.
grabbing_or_dropping
()):
direction
=
RIGHT
if
diff
>
0
else
LEFT
yield
from
src
.
gen_stationary
()
return
abs
(
diff
)
*
(
direction
,)
for
get
in
src
.
gen_get
():
ignore_exa_column
=
self
.
grabbing_or_dropping
()
for
dst
in
get
.
gen_shift
(
False
):
yield
from
dst
.
gen_put
()
for
src
in
range
(
COLUMNS
):
mov1
=
shift_exa
(
src
-
self
.
exa
)
def
gen_shift
(
self
,
allow_noshift
):
if
mov1
or
not
ignore_exa_column
:
if
allow_noshift
:
yield
mov1
+
(
SWAP
,)
yield
self
yield
mov1
+
(
GRAB
,
SWAP
,
DROP
)
yield
mov1
+
(
SWAP
,
GRAB
,
SWAP
,
DROP
)
left
=
self
yield
mov1
+
(
GRAB
,
SWAP
,
DROP
,
SWAP
)
for
i
in
range
(
self
.
exa
):
yield
mov1
+
(
SWAP
,
GRAB
,
SWAP
,
DROP
,
SWAP
)
left
=
left
.
move
(
LEFT
)
yield
left
for
dst
in
range
(
COLUMNS
):
if
dst
!=
src
:
right
=
self
mov2
=
shift_exa
(
dst
-
src
)
for
i
in
range
(
COLUMNS
-
self
.
exa
-
1
):
for
get
in
GET
:
right
=
right
.
move
(
RIGHT
)
for
put
in
PUT
:
yield
right
yield
mov1
+
get
+
mov2
+
put
def
gen_stationary
(
self
):
def
gen_valid_moves
(
self
):
# SWAP
for
moves
in
self
.
gen_moves
():
# GRAB, SWAP, DROP
try
:
# GRAB, SWAP, DROP, SWAP
yield
self
.
move
(
moves
)
# SWAP, GRAB, SWAP, DROP
except
AssertionError
:
# SWAP, GRAB, SWAP, DROP, SWAP
pass
if
not
self
.
colbusy
(
self
.
exa
):
avail
=
self
.
colrows
(
self
.
exa
)
if
avail
>=
2
:
swap
=
self
.
move
(
SWAP
)
yield
swap
if
avail
>=
3
:
grab
=
self
.
move
(
GRAB
,
SWAP
,
DROP
)
yield
grab
yield
grab
.
move
(
SWAP
)
swap
=
swap
.
move
(
GRAB
,
SWAP
,
DROP
)
yield
swap
yield
swap
.
move
(
SWAP
)
def
gen_get
(
self
):
# GRAB
# SWAP, GRAB
# GRAB, SWAP, DROP, SWAP, GRAB
if
not
self
.
colbusy
(
self
.
exa
):
avail
=
self
.
colrows
(
self
.
exa
)
if
avail
>=
1
:
grab
=
self
.
move
(
GRAB
)
yield
grab
if
avail
>=
2
:
yield
self
.
move
(
SWAP
,
GRAB
)
if
avail
>=
3
:
yield
grab
.
move
(
SWAP
,
DROP
,
SWAP
,
GRAB
)
def
gen_put
(
self
):
# DROP
# DROP, SWAP
# DROP, SWAP, GRAB, SWAP, DROP
if
not
self
.
colbusy
(
self
.
exa
):
avail
=
self
.
colrows
(
self
.
exa
)
drop
=
self
.
move
(
DROP
)
yield
drop
if
avail
>=
1
:
swap
=
drop
.
move
(
SWAP
)
yield
swap
if
avail
>=
2
:
yield
swap
.
move
(
GRAB
,
SWAP
,
DROP
)
def
force
(
self
,
*
moves
):
state
=
self
.
move
(
*
moves
)
state
.
score
=
()
return
state
def
solve
(
self
):
def
solve
(
self
):
assert
self
.
exa
is
not
None
assert
self
.
exa
is
not
None
if
self
.
held
!=
NOBLOCK
:
if
self
.
held
!=
NOBLOCK
:
return
self
.
move
((
DROP
,)
)
return
self
.
force
(
DROP
)
valid
=
deque
(
self
.
gen_valid
_moves
())
pool
=
deque
(
self
.
gen
_moves
())
if
len
(
valid
)
==
0
:
if
len
(
pool
)
==
0
:
return
self
.
move
(()
)
return
self
.
force
(
)
best_score
=
()
best_score
=
()
for
key
in
self
.
score_keys
():
for
key
in
self
.
score_keys
():
if
len
(
valid
)
==
1
:
if
len
(
pool
)
==
1
:
break
break
for
state
in
valid
:
for
state
in
pool
:
state
.
score
=
key
(
state
)
state
.
score
=
key
(
state
)
best
=
min
(
state
.
score
for
state
in
valid
)
best
=
min
(
state
.
score
for
state
in
pool
)
best_score
+=
(
best
,)
best_score
+=
(
best
,)
for
i
in
range
(
len
(
valid
)):
for
i
in
range
(
len
(
pool
)):
state
=
valid
.
popleft
()
state
=
pool
.
popleft
()
if
state
.
score
==
best
:
if
state
.
score
==
best
:
valid
.
append
(
state
)
pool
.
append
(
state
)
best
=
valid
.
popleft
()
best
=
pool
.
popleft
()
best
.
score
=
best_score
best
.
score
=
best_score
return
best
return
best
...
@@ -344,9 +397,6 @@ class State:
...
@@ -344,9 +397,6 @@ class State:
def
keys
(
self
):
def
keys
(
self
):
return
moves_to_keys
(
self
.
moves
)
return
moves_to_keys
(
self
.
moves
)
def
__lt__
(
self
,
other
):
return
self
.
score
<
other
.
score
def
loops
(
self
,
prev
):
def
loops
(
self
,
prev
):
return
self
.
moves
and
\
return
self
.
moves
and
\
self
.
exa
==
prev
.
exa
and
\
self
.
exa
==
prev
.
exa
and
\
...
@@ -354,6 +404,25 @@ class State:
...
@@ -354,6 +404,25 @@ class State:
self
.
score
==
prev
.
score
self
.
score
==
prev
.
score
def
get_colskip
(
blocks
):
def
colskip
(
col
):
for
row
,
block
in
enumerate
(
blocks
[
col
::
COLUMNS
]):
if
block
!=
NOBLOCK
:
return
row
return
len
(
blocks
)
//
COLUMNS
return
list
(
map
(
colskip
,
range
(
COLUMNS
)))
def
get_busy
(
blocks
,
colskip
):
mask
=
0
for
col
,
skip
in
enumerate
(
colskip
):
start
=
(
skip
+
1
)
*
COLUMNS
+
col
colbusy
=
NOBLOCK
in
blocks
[
start
::
COLUMNS
]
mask
|=
colbusy
<<
col
return
mask
def
move_to_key
(
move
):
def
move_to_key
(
move
):
return
'jjkadl'
[
move
]
return
'jjkadl'
[
move
]
...
@@ -387,3 +456,8 @@ if __name__ == '__main__':
...
@@ -387,3 +456,8 @@ if __name__ == '__main__':
print
(
'target after move:'
)
print
(
'target after move:'
)
newstate
.
print
()
newstate
.
print
()
#print()
#print('generated moves:')
#for state in state.gen_moves():
# print(state.keys())
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment