嗯,这不完全是一般问题,但这就是我们毕竟在这里的原因。
有点背景
我的任务是创建一个类似仿真的俄罗斯方块,用AI播放它。线条完成后不会消失。最终结果应该是一个填充了整齐排列的块的矩阵,很少或没有间隙。
我选择做的是一种遗传方法,具有恒定的权重和评估方法。 AI会尝试将块放在所有可能的位置,旋转,评估临时矩阵,并选择最好的矩阵。
问题
在俄罗斯方块中,即使挡块在地面上,您也可以向左或向右移动。这允许解决许多本来不可能的位置。然而真正的问题是,这些洞甚至可以在半空中发生,如下所示:
falling J shape, with optimal choice occurring mid-air
我在这里看到的唯一解决方案是尝试所有位置,旋转和所有可能的空中移动组合,我认为这是"不是最佳解决方案"正式说出来。
我的问题
如果有人有想法或其他方法,那么找到具有实际计算能力的放置的可能性
答案 0 :(得分:0)
在一块板上最多可以放置10x20x4 = 800个位置。这些将是图形的节点。您可以单步从一个节点移动到另一个节点,这里有一个优势。然后,您可以修剪非法节点(例如,与板上现有的障碍物重叠)。您也可以将节点标记为“最终”-在那里,图块的至少一部分正下方有障碍物(但它们仍然可以连接节点)。
然后您可以检查从初始节点可以到达哪些最终节点,这是一个标准问题。
您可以通过忽略超出当前板高度的节点来进一步优化此效果。
示例代码:
import copy
import time
from os import system, name
from random import randint
from wrapt import synchronized
from asciimatics.screen import Screen
import asciimatics
from asciimatics.effects import Cycle, Stars
from asciimatics.renderers import FigletText
from asciimatics.scene import Scene
from asciimatics.screen import Screen
class Tile:
shapes = \
[
[
[0, 0, 2],
[2, 2, 2]
],
[
[3, 3, 0],
[0, 3, 3]
],
[
[0, 4, 4],
[4, 4, 0]
],
[
[5, 0, 0],
[5, 5, 5]
],
[
[6, 6],
[6, 6]
],
[
[0, 0, 0, 0],
[7, 7, 7, 7],
[0, 0, 0, 0]
],
[
[0, 8, 0],
[8, 8, 8]
]
]
def __init__(self, id=-1):
if id >= 0:
self.id = id
else:
self.id = randint(0, len(self.shapes)-1)
self.shape = self.shapes[id]
x = 8
y = 0
id = 0
def rotate(self):
self.shape = list(zip(*self.shape[::-1]))
class Model:
_height = 25
_width = 20
_score = 0
def __init__(self):
self._view = None
self._field = [[0] * self._width for i in range(self._height)]
for i in range(5):
for j in range(self._height):
self._field[j][i] = 1
self._field[j][-i-1] = 1
for i in range(5):
for j in range(self._width):
self._field[-i-1][j] = 1
self._tile = Tile()
self._nexttile = Tile()
def set_view(self, view):
self._view = view
def get_height(self):
i = 0
for r in self._field[:-5]:
full_line = True
if sum(r[5:-5]) > 0:
return i
i += 1
return i
def _merge(self, field, tile):
field_copy = copy.deepcopy(field)
for i in range(len(tile.shape)):
for j in range(len(tile.shape[0])):
field_copy[tile.y + i][tile.x + j] += tile.shape[i][j]
return field_copy
@synchronized
def _is_valid(self, field, tile):
for i in range(len(tile.shape)):
for j in range(len(tile.shape[0])):
if tile.shape[i][j] > 0:
if (field[tile.y + i][tile.x + j] > 0):
return False
return True
def get_board(self):
return self._merge(self._field, self._tile)
def rotate(self):
self._tile.rotate()
if not self._is_valid(self._field, self._tile):
self._tile.rotate()
self._tile.rotate()
self._tile.rotate()
if self._view is not None:
self._view.show()
def _which_lines_completed(self):
lines = []
i = 0
for r in self._field[:-5]:
full_line = True
for c in r:
if c == 0:
full_line = False
if full_line:
lines.append(i)
i += 1
return lines
def _remove_lines(self, lines):
for l in lines:
for i in list(range(1, l+1))[::-1]:
self._field[i] = self._field[i-1].copy()
if len(lines) == 4:
self._score += 5000
elif len(lines) == 3:
self._score += 1000
elif len(lines) == 2:
self._score += 500
elif len(lines) == 1:
self._score += 100
@synchronized
def move_down(self):
self._tile.y += 1
if not self._is_valid(self._field, self._tile):
self._tile.y -= 1
self._field = self._merge(self._field, self._tile)
self._tile = self._nexttile
self._nexttile = Tile()
# Check if any lines need to be removed
lines = self._which_lines_completed()
# If lines need to be removed, notify the view
if len(lines) > 0:
self._view.remove_lines(lines)
# Remove the lines
self._remove_lines(lines)
if self._view is not None:
self._view.show()
@synchronized
def move_left(self):
self._tile.x -= 1
if not self._is_valid(self._field, self._tile):
self._tile.x += 1
else:
if self._view is not None:
self._view.show()
@synchronized
def move_right(self):
self._tile.x += 1
if not self._is_valid(self._field, self._tile):
self._tile.x -= 1
if self._view is not None:
self._view.show()
class AsciimaticView:
def __init__(self, model):
self.screen = Screen.open()
self.model = model
def _show_board(self, board):
#self.screen.clear()
b = board
x = 0
y = 0
for r in b[:-4]:
x = 0
for c in r[4:-4]:
if c == 1:
self.screen.print_at(u'██', x, y, Screen.COLOUR_BLUE, Screen.A_BOLD)
elif c == 2:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_RED, Screen.A_BOLD)
elif c == 3:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_GREEN, Screen.A_BOLD)
elif c == 4:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_GREEN, Screen.A_BOLD)
elif c == 5:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_RED, Screen.A_BOLD)
elif c == 6:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_CYAN, Screen.A_BOLD)
elif c == 7:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_YELLOW, Screen.A_BOLD)
elif c == 8:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_MAGENTA, Screen.A_BOLD)
else:
self.screen.print_at(u' ', x, y, Screen.COLOUR_BLUE, Screen.A_BOLD)
x += 2
y += 1
self.screen.print_at(u' ', 0, y, Screen.COLOUR_RED, Screen.A_BOLD)
self.screen.print_at(u' ', 0, y+1, Screen.COLOUR_RED, Screen.A_BOLD)
self.screen.print_at(u' ', 0, y+2, Screen.COLOUR_RED, Screen.A_BOLD)
self.screen.print_at(u' ', 0, y+3, Screen.COLOUR_RED, Screen.A_BOLD)
for i in range(len(self.model._nexttile.shape)):
x = 0
if (i == 1):
self.screen.print_at(u'Next: ', x, y, Screen.COLOUR_WHITE, Screen.A_BOLD)
x = x + 6
for j in range(len(self.model._nexttile.shape[0])):
c = self.model._nexttile.shape[i][j]
if c == 1:
self.screen.print_at(u'██', x, y, Screen.COLOUR_BLUE, Screen.A_BOLD)
elif c == 2:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_RED, Screen.A_BOLD)
elif c == 3:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_GREEN, Screen.A_BOLD)
elif c == 4:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_GREEN, Screen.A_BOLD)
elif c == 5:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_RED, Screen.A_BOLD)
elif c == 6:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_CYAN, Screen.A_BOLD)
elif c == 7:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_YELLOW, Screen.A_BOLD)
elif c == 8:
self.screen.print_at(u'[]', x, y, Screen.COLOUR_MAGENTA, Screen.A_BOLD)
else:
self.screen.print_at(u' ', x, y, Screen.COLOUR_BLUE, Screen.A_BOLD)
x = x + 2
y = y + 1
x = 0
y = 24
self.screen.print_at(u'Score: ' + str(self.model._score), x, y, Screen.COLOUR_WHITE, Screen.A_BOLD)
self.screen.refresh()
def show(self):
self._show_board(self.model.get_board())
def remove_lines(self, lines):
b = self.model.get_board()
for i in range(5):
for l in lines:
b[l][5:-5] = [1-el for el in b[l][5:-5]]
self._show_board(b)
time.sleep(0.1)
class Node:
x = 0
y = 0
rot = 0
final = False
edges = []
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Node):
return (self.x == other.x) and (self.y == other.y) and (self.rot == other.rot)
return False
def is_neighbour(self, other):
if (abs(self.x - other.x) + abs(self.y - other.y) + abs(self.rot - other.rot) == 1) and (other.y >= self.y):
return True
return False
def __hash__(self):
return hash((self.x, self.y, self.rot))
def get_possible_moves(model, tile):
start_node = Node()
start_node.x = model._tile.x
start_node.y = model.get_height() - len(tile.shape)-1
frontier = [start_node]
visited = {start_node: True}
final_nodes = []
while len(frontier) > 0:
n = frontier.pop()
for dx, dy, rot in [(-1, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)][::-1]:
nn = Node()
nn.x = n.x + dx
nn.y = n.y + dy
nn.rot = (n.rot + rot) % 4
if nn not in visited:
visited[nn] = True
t = Tile(tile.id)
t.x = nn.x
t.y = nn.y
for r in range(nn.rot):
t.rotate()
if model._is_valid(model._field, t):
frontier.append(nn)
# check if node is final
for i in range(len(t.shape)):
for j in range(len(t.shape[0])):
if (t.shape[i][j] > 0) and (model._field[nn.y + i + 1][nn.x + j] > 0):
nn.final = True
final_nodes.append(nn)
break
if (nn.final):
break
print(len(visited))
print(len(final_nodes))
return final_nodes
m = Model()
m._field = [
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 3, 0, 3, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 0, 0, 0, 0, 3, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
m._tile = Tile(3)
import threading
import keyboard
# define a thread which takes input
class InputThread(threading.Thread):
def run(self):
#self.daemon = True
self.last_user_input = None
while True:
try:
if keyboard.is_pressed('left'):#if key 'q' is pressed
self.last_user_input = 'a'
m.move_left()
elif keyboard.is_pressed('right'):#if key 'q' is pressed
self.last_user_input = 'a'
m.move_right()
elif keyboard.is_pressed('z'):
print('z')
self.last_user_input = 'z'
m.rotate()
elif keyboard.is_pressed('down'):
m.move_down()
elif keyboard.is_pressed('q'):
break
else:
pass
# do something based on the user input here
# alternatively, let main do something with
# self.last_user_input
except:
pass
time.sleep(0.1)
# main
#it = InputThread()
#it.start()
fn = get_possible_moves(m, m._tile)
time.sleep(2)
v = AsciimaticView(m)
m.set_view(v)
v.show()
time.sleep(2)
for n in fn:
m._tile = Tile(m._tile.id)
m._tile.x = n.x
m._tile.y = n.y
for r in range(n.rot):
m._tile.rotate()
v.show()
time.sleep(1)
time.sleep(500)