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
bdbd073a
Commit
bdbd073a
authored
Apr 04, 2020
by
Taddeüs Kroes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add strategy solver and first working bot
parent
770a09bf
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
376 additions
and
0 deletions
+376
-0
bot.py
bot.py
+55
-0
strategy.py
strategy.py
+321
-0
No files found.
bot.py
0 → 100755
View file @
bdbd073a
#!/usr/bin/env python3
import
time
from
strategy
import
State
,
moves_to_keys
from
interaction
import
get_exapunks_window
,
focus_window
,
\
screenshot_board
,
press_keys
if
__name__
==
'__main__'
:
win
=
get_exapunks_window
()
focus_window
(
win
)
prev_score
=
None
while
True
:
board
=
screenshot_board
(
win
)
try
:
state
=
State
.
detect
(
board
)
except
(
TypeError
,
AssertionError
):
print
(
'error during parsing, wait for a bit'
)
time
.
sleep
(.
1
)
continue
print
(
'
\
033
c'
,
end
=
''
)
print
(
'parsed:'
)
state
.
print
()
print
()
#if state.has_holes():
# print('parsed board has holes, wait for a bit')
# time.sleep(.1)
# continue
moves
=
state
.
solve
()
if
moves
:
print
(
'moves:'
,
moves_to_keys
(
moves
))
points
,
newstate
=
state
.
simulate
(
moves
)
score
=
newstate
.
score
(
points
,
moves
,
state
)
print
(
' score:'
,
score
)
print
(
'prev score:'
,
prev_score
)
empty_points
,
empty_state
=
state
.
simulate
(())
print
(
' () score:'
,
empty_state
.
score
(
empty_points
,
(),
state
))
print
()
prev_score
=
score
print
(
'target after moves:'
)
newstate
.
print
()
print
()
press_keys
(
win
,
moves_to_keys
(
moves
))
else
:
print
(
'no moves'
)
press_keys
(
win
,
'l'
)
time
.
sleep
(.
1
)
strategy.py
0 → 100644
View file @
bdbd073a
import
time
from
collections
import
deque
from
itertools
import
islice
from
parser
import
COLUMNS
,
NOBLOCK
,
detect_blocks
,
detect_exa
,
\
detect_held
,
print_board
,
is_basic
,
is_bomb
,
bomb_to_basic
GRAB
,
DROP
,
SWAP
,
LEFT
,
RIGHT
=
range
(
5
)
GET
=
((
GRAB
,),
(
SWAP
,
GRAB
),
(
GRAB
,
SWAP
,
DROP
,
SWAP
,
GRAB
))
PUT
=
((
DROP
,),
(
DROP
,
SWAP
),
(
DROP
,
SWAP
,
GRAB
,
SWAP
,
DROP
))
#REVERSE = [DROP, GRAB, SWAP, RIGHT, LEFT]
MIN_BASIC_GROUP_SIZE
=
4
MIN_BOMB_GROUP_SIZE
=
2
FIND_GROUPS_DEPTH
=
20
FRAG_DEPTH
=
20
COLSIZE_PRIO
=
5
class
State
:
def
__init__
(
self
,
blocks
,
exa
,
held
):
assert
exa
is
not
None
self
.
blocks
=
blocks
self
.
exa
=
exa
self
.
held
=
held
def
has_holes
(
self
):
return
any
(
self
.
blocks
[
i
]
==
NOBLOCK
for
col
in
self
.
iter_columns
()
for
i
in
col
)
def
iter_columns
(
self
):
nrows
=
self
.
nrows
()
def
gen_col
(
col
):
for
row
in
range
(
nrows
):
i
=
row
*
COLUMNS
+
col
if
self
.
blocks
[
i
]
!=
NOBLOCK
:
yield
i
for
col
in
range
(
COLUMNS
):
yield
gen_col
(
col
)
@
classmethod
def
detect
(
cls
,
board
):
blocks
=
[
NOBLOCK
]
*
(
COLUMNS
*
2
)
+
list
(
detect_blocks
(
board
))
exa
=
detect_exa
(
board
)
held
=
detect_held
(
board
,
exa
)
return
cls
(
blocks
,
exa
,
held
)
def
copy
(
self
):
return
State
(
list
(
self
.
blocks
),
self
.
exa
,
self
.
held
)
def
colsizes
(
self
):
for
col
in
range
(
COLUMNS
):
yield
self
.
nrows
()
-
self
.
colskip
(
col
)
def
score
(
self
,
points
,
moves
,
prev
):
frag
=
self
.
fragmentation
()
colsizes
=
list
(
self
.
colsizes
())
mincol
=
min
(
colsizes
)
maxcol
=
max
(
colsizes
)
colsize_score
=
maxcol
,
colsizes
.
count
(
maxcol
),
-
mincol
#colsize_score = tuple(sorted(colsizes, reverse=True))
#if prev.nrows() >= 6:
# return colsize_score, -points, frag, len(moves)
prev_colsize
=
max
(
prev
.
colsizes
())
if
prev_colsize
>=
COLSIZE_PRIO
:
return
colsize_score
,
frag
,
-
points
,
len
(
moves
)
else
:
return
-
points
,
frag
,
colsize_score
,
len
(
moves
)
def
score_moves
(
self
):
# clear exploding blocks before computing colsize
prev
=
self
.
copy
()
prev
.
score_points
()
for
moves
in
self
.
gen_moves
():
try
:
points
,
newstate
=
self
.
simulate
(
moves
)
yield
newstate
.
score
(
points
,
moves
,
prev
),
moves
except
AssertionError
:
pass
def
colskip
(
self
,
col
):
nrows
=
self
.
nrows
()
for
row
in
range
(
nrows
):
if
self
.
blocks
[
row
*
COLUMNS
+
col
]
!=
NOBLOCK
:
return
row
return
nrows
def
find_unmovable_blocks
(
self
):
unmoveable
=
set
()
bombed
=
set
()
for
block
,
group
in
self
.
find_groups
():
if
is_basic
(
block
)
and
len
(
group
)
>=
MIN_BASIC_GROUP_SIZE
:
for
i
in
group
:
unmoveable
.
add
(
i
)
elif
is_bomb
(
block
)
and
len
(
group
)
>=
MIN_BOMB_GROUP_SIZE
:
bombed
.
add
(
bomb_to_basic
(
block
))
for
i
in
group
:
unmoveable
.
add
(
i
)
for
i
,
block
in
enumerate
(
self
.
blocks
):
if
block
in
bombed
:
unmoveable
.
add
(
i
)
return
unmoveable
def
simulate
(
self
,
moves
):
s
=
self
.
copy
()
points
=
0
# clear the current board before planning the next move
#s.score_points()
if
not
moves
:
return
s
.
score_points
(),
s
# avoid swapping/grabbing currently exploding items
unmoveable
=
s
.
find_unmovable_blocks
()
for
move
in
moves
:
if
move
==
LEFT
:
assert
s
.
exa
>
0
s
.
exa
-=
1
elif
move
==
RIGHT
:
assert
s
.
exa
<
COLUMNS
-
1
s
.
exa
+=
1
elif
move
==
GRAB
:
assert
s
.
held
==
NOBLOCK
row
=
s
.
colskip
(
s
.
exa
)
assert
row
<
s
.
nrows
()
i
=
row
*
COLUMNS
+
s
.
exa
assert
i
not
in
unmoveable
s
.
held
=
s
.
blocks
[
i
]
s
.
blocks
[
i
]
=
NOBLOCK
elif
move
==
DROP
:
assert
s
.
held
!=
NOBLOCK
row
=
s
.
colskip
(
s
.
exa
)
assert
row
>
0
i
=
row
*
COLUMNS
+
s
.
exa
s
.
blocks
[
i
-
COLUMNS
]
=
s
.
held
s
.
held
=
NOBLOCK
points
+=
s
.
score_points
()
elif
move
==
SWAP
:
row
=
s
.
colskip
(
s
.
exa
)
assert
row
<
s
.
nrows
()
-
2
i
=
row
*
COLUMNS
+
s
.
exa
j
=
i
+
COLUMNS
assert
i
not
in
unmoveable
assert
j
not
in
unmoveable
s
.
blocks
[
i
],
s
.
blocks
[
j
]
=
s
.
blocks
[
j
],
s
.
blocks
[
i
]
points
+=
s
.
score_points
()
return
points
,
s
def
find_groups
(
self
,
depth
=
FIND_GROUPS_DEPTH
):
def
follow_group
(
i
,
block
,
group
):
if
self
.
blocks
[
i
]
==
block
and
i
not
in
visited
:
group
.
append
(
i
)
visited
.
add
(
i
)
for
nb
in
self
.
neighbors
(
i
):
follow_group
(
nb
,
block
,
group
)
visited
=
set
()
for
col
in
self
.
iter_columns
():
for
i
in
islice
(
col
,
depth
):
block
=
self
.
blocks
[
i
]
group
=
[]
follow_group
(
i
,
block
,
group
)
if
len
(
group
)
>
1
:
#for j in group:
# visited.add(j)
yield
block
,
group
def
neighbors
(
self
,
i
):
def
gen_indices
():
row
,
col
=
divmod
(
i
,
COLUMNS
)
if
col
>
0
:
yield
i
-
1
if
col
<
COLUMNS
-
1
:
yield
i
+
1
if
row
>
0
:
yield
i
-
COLUMNS
if
row
<
self
.
nrows
()
-
1
:
yield
i
+
COLUMNS
for
j
in
gen_indices
():
if
self
.
blocks
[
j
]
!=
NOBLOCK
:
yield
j
def
fragmentation
(
self
,
depth
=
FRAG_DEPTH
):
"""
Sum the minimum distance from every block in the first 3 layers to the
closest block of the same color.
"""
def
find_closest
(
i
):
block
=
self
.
blocks
[
i
]
work
=
deque
([(
i
,
-
1
)])
visited
=
{
i
}
while
work
:
i
,
dist
=
work
.
popleft
()
if
dist
>=
0
and
self
.
blocks
[
i
]
==
block
:
return
dist
for
j
in
self
.
neighbors
(
i
):
if
j
not
in
visited
:
visited
.
add
(
j
)
work
.
append
((
j
,
dist
+
1
))
# only one of this type -> don't count as fragmented
return
0
return
sum
(
find_closest
(
i
)
for
col
in
self
.
iter_columns
()
for
i
in
islice
(
col
,
depth
))
def
score_points
(
self
,
multiplier
=
1
):
remove
=
[]
points
=
0
for
block
,
group
in
self
.
find_groups
():
if
is_basic
(
block
)
and
len
(
group
)
>=
MIN_BASIC_GROUP_SIZE
:
remove
.
extend
(
group
)
points
+=
len
(
group
)
*
multiplier
elif
is_bomb
(
block
)
and
len
(
group
)
>=
MIN_BOMB_GROUP_SIZE
:
#points += 10
remove
.
extend
(
group
)
for
i
,
other
in
enumerate
(
self
.
blocks
):
if
other
==
bomb_to_basic
(
block
):
remove
.
append
(
i
)
remove
.
sort
()
prev
=
None
for
i
in
remove
:
if
i
!=
prev
:
while
self
.
blocks
[
i
]
!=
NOBLOCK
:
self
.
blocks
[
i
]
=
self
.
blocks
[
i
-
COLUMNS
]
i
-=
COLUMNS
prev
=
i
if
points
:
points
+=
self
.
score_points
(
min
(
2
,
multiplier
*
2
))
return
points
def
gen_moves
(
self
):
yield
()
for
src
in
range
(
COLUMNS
):
diff
=
src
-
self
.
exa
direction
=
RIGHT
if
diff
>
0
else
LEFT
mov1
=
abs
(
diff
)
*
(
direction
,)
yield
mov1
+
(
SWAP
,)
yield
mov1
+
(
GRAB
,
SWAP
,
DROP
)
for
dst
in
range
(
COLUMNS
):
diff
=
dst
-
src
direction
=
RIGHT
if
diff
>
0
else
LEFT
mov2
=
abs
(
diff
)
*
(
direction
,)
for
i
,
get
in
enumerate
(
GET
):
if
i
>
1
or
diff
!=
0
:
for
put
in
PUT
:
yield
mov1
+
get
+
mov2
+
put
def
solve
(
self
):
if
self
.
held
!=
NOBLOCK
:
return
(
DROP
,)
score
,
moves
=
min
(
self
.
score_moves
())
return
moves
def
print
(
self
):
print_board
(
self
.
blocks
,
self
.
exa
,
self
.
held
)
def
nrows
(
self
):
return
len
(
self
.
blocks
)
//
COLUMNS
def
moves_to_keys
(
moves
):
return
''
.
join
(
'jjkad'
[
move
]
for
move
in
moves
)
if
__name__
==
'__main__'
:
import
sys
from
PIL
import
Image
#from pprint import pprint
board
=
Image
.
open
(
'screens/board%d.png'
%
int
(
sys
.
argv
[
1
])).
convert
(
'HSV'
)
state
=
State
.
detect
(
board
)
print
(
'parsed:'
)
state
.
print
()
print
()
start
=
time
.
time
()
moves
=
state
.
solve
()
print
(
'moves:'
,
moves_to_keys
(
moves
))
end
=
time
.
time
()
print
(
'elapsed:'
,
end
-
start
)
print
()
print
(
'target after moves:'
)
points
,
newstate
=
state
.
simulate
(
moves
)
newstate
.
print
()
print
()
#for score, moves in sorted(state.score_moves()):
# print('move %18s:' % moves_to_keys(moves), score)
# #print('moves:', moves_to_keys(moves), moves)
# #print('score:', score)
#print('\nmoves:', moves_to_keys(state.solve()))
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