Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/330.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 俄罗斯方块随机生成器:随机选择与随机洗牌_Python_Algorithm_Random - Fatal编程技术网

Python 俄罗斯方块随机生成器:随机选择与随机洗牌

Python 俄罗斯方块随机生成器:随机选择与随机洗牌,python,algorithm,random,Python,Algorithm,Random,在俄罗斯方块游戏的一些实现中,有一种称为的算法,它根据以下算法生成一组单面俄罗斯方块的无限序列排列: 随机生成器生成所有七个单面序列 tetrominoes(I,J,L,O,S,T,Z)随机排列,就好像它们是 从袋子里抽出来的。然后它把所有的七个特特罗米诺都处理成一块 在生成另一个包之前进行排序 此无限序列的元素仅在必要时生成。i、 e.当需要的碎片超过队列所能提供的数量时,将7个单侧四极体的随机排列附加到四极体队列中 我相信在Python中有两种主要的实现方法 第一种方法使用itertools

在俄罗斯方块游戏的一些实现中,有一种称为的算法,它根据以下算法生成一组单面俄罗斯方块的无限序列排列:

随机生成器生成所有七个单面序列 tetrominoes(I,J,L,O,S,T,Z)随机排列,就好像它们是 从袋子里抽出来的。然后它把所有的七个特特罗米诺都处理成一块 在生成另一个包之前进行排序

此无限序列的元素仅在必要时生成。i、 e.当需要的碎片超过队列所能提供的数量时,将7个单侧四极体的随机排列附加到四极体队列中

我相信在Python中有两种主要的实现方法

第一种方法使用
itertools.permutations
random.choice

import itertools, random, collections
bag = "IJLOSTZ"
bigbag = list(itertools.permutations(bag))
sequence = collections.deque(random.choice(bigbag))
sequence.extend(random.choice(bigbag))
sequence.extend(random.choice(bigbag))
# . . . Extend as necessary
第二种方法只使用
random.shuffle

import random, collections
bag = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']
random.shuffle(bag)
sequence = collections.deque(bag)
random.shuffle(bag)
sequence.extend(bag)
random.shuffle(bag)
sequence.extend(bag)
# . . . Extend as necessary

假设俄罗斯方块玩家熟练,并且随机生成器必须产生大量单侧俄罗斯方块序列,那么这两种方法的优缺点是什么?

7!=5040
7个不同对象序列的排列。因此,就时间复杂度(O(n!*n))和空间复杂度(O(n!*n))而言,生成所有置换是非常昂贵的。然而,从排列序列中选择一个随机排列是很容易的。让我们看一下来自的
选项的代码

正如您所见,索引的计算是O(1),因为
len(seq)
对于任何序列都是O(1),而
self.random()
也是O(1)。在Python中,从列表类型获取元素也是O(1),因此整个函数是O(1)


另一方面,使用
random.shuffle
可以将包中的元素交换到位。因此,它将使用O(1)空间复杂度。然而,就时间复杂度而言,它不是那么有效。让我们看一下来自的
shuffle
的代码

random.shuffle
实现了which,“类似于从帽子中随机挑选带编号的票子,或从牌堆中一张接一张地挑选卡片,直到没有剩余为止。”然而,由于
len(x)-1
调用
random(),计算的数量明显大于第一种方法
必须进行,并且还需要
len(x)-1
交换操作。每个交换操作都需要从列表中提取两次数据,并生成一个用于解包和赋值的2元组


基于所有这些信息,我猜前面提到的第一种方法会占用大量内存来存储排列,并且需要O(n!*n)时间复杂度开销,但从长远来看,它可能比第二种方法效率更高,并且在俄罗斯方块游戏的实际实现中可能会保持帧速率稳定,因为在实际的游戏循环中要做的计算更少。排列可以在显示初始化之前生成,这有助于产生一种错觉,即游戏不会执行很多计算


在这里,我使用TimPeters关于生成器和循环缓冲区的建议发布最终代码。因为循环缓冲区的大小在创建缓冲区之前就已经知道了,而且它永远不会改变,所以我没有实现循环缓冲区通常具有的所有特性(您可以在上找到)。在任何情况下,它都适用于随机生成器算法

def random_generator():
    import itertools, random
    bag = "IJLOSTZ"
    bigbag = list(itertools.permutations(bag))
    while True:
        for ost in random.choice(bigbag):
            yield ost


def popleft_append(buff, start_idx, it):
    """ This function emulates popleft and append from
        collections.deque all in one step for a circular buffer
        of size n which is always full,
        The argument to parameter "it" must be an infinite 
        generator iterable, otherwise it.next() may throw
        an exception at some point """
    left_popped = buff[start_idx]
    buff[start_idx] = it.next()
    return (start_idx + 1) % len(buff), left_popped


def circular_peek(seq, start_idx):
    return seq[start_idx:len(seq)] + seq[:start_idx]


# Example usage for peek queue of size 5
rg = random_generator()
pqsize = 5
# Initialize buffer using pqsize elements from generator
buff = [rg.next() for _ in xrange(pqsize)]
start_idx = 0
# Game loop
while True:
    # Popping one OST (one-sided tetromino) from queue and 
    # adding a new one, also updating start_idx
    start_idx, left_popped = popleft_append(buff, start_idx, rg)
    # To show the peek queue currently
    print circular_peek(buff, start_idx)

7!=5040
7个不同对象序列的排列。因此,就时间复杂度(O(n!*n))和空间复杂度(O(n!*n))而言,生成所有置换是非常昂贵的。然而,从排列序列中选择一个随机排列是很容易的。让我们看一下来自的
选项的代码

正如您所见,索引的计算是O(1),因为
len(seq)
对于任何序列都是O(1),而
self.random()
也是O(1)。在Python中,从列表类型获取元素也是O(1),因此整个函数是O(1)


另一方面,使用
random.shuffle
可以将包中的元素交换到位。因此,它将使用O(1)空间复杂度。然而,就时间复杂度而言,它不是那么有效。让我们看一下来自的
shuffle
的代码

random.shuffle
实现了which,“类似于从帽子中随机挑选带编号的票子,或从牌堆中一张接一张地挑选卡片,直到没有剩余为止。”然而,由于
len(x)-1
调用
random(),计算的数量明显大于第一种方法
必须进行,并且还需要
len(x)-1
交换操作。每个交换操作都需要从列表中提取两次数据,并生成一个用于解包和赋值的2元组


基于所有这些信息,我猜前面提到的第一种方法会占用大量内存来存储排列,并且需要O(n!*n)时间复杂度开销,但从长远来看,它可能比第二种方法效率更高,并且在俄罗斯方块游戏的实际实现中可能会保持帧速率稳定,因为在实际的游戏循环中要做的计算更少。排列可以在显示初始化之前生成,这有助于产生一种错觉,即游戏不会执行很多计算


在这里,我使用TimPeters关于生成器和循环缓冲区的建议发布最终代码。因为循环缓冲区的大小在创建缓冲区之前就已经知道了,而且它永远不会改变,所以我没有实现循环缓冲区通常具有的所有特性(您可以在上找到)。在任何情况下,它都适用于随机生成器算法

def random_generator():
    import itertools, random
    bag = "IJLOSTZ"
    bigbag = list(itertools.permutations(bag))
    while True:
        for ost in random.choice(bigbag):
            yield ost


def popleft_append(buff, start_idx, it):
    """ This function emulates popleft and append from
        collections.deque all in one step for a circular buffer
        of size n which is always full,
        The argument to parameter "it" must be an infinite 
        generator iterable, otherwise it.next() may throw
        an exception at some point """
    left_popped = buff[start_idx]
    buff[start_idx] = it.next()
    return (start_idx + 1) % len(buff), left_popped


def circular_peek(seq, start_idx):
    return seq[start_idx:len(seq)] + seq[:start_idx]


# Example usage for peek queue of size 5
rg = random_generator()
pqsize = 5
# Initialize buffer using pqsize elements from generator
buff = [rg.next() for _ in xrange(pqsize)]
start_idx = 0
# Game loop
while True:
    # Popping one OST (one-sided tetromino) from queue and 
    # adding a new one, also updating start_idx
    start_idx, left_popped = popleft_append(buff, start_idx, rg)
    # To show the peek queue currently
    print circular_peek(buff, start_idx)

7!=5040
7个不同对象序列的排列。因此,生成
def random_generator():
    import itertools, random
    bag = "IJLOSTZ"
    bigbag = list(itertools.permutations(bag))
    while True:
        for ost in random.choice(bigbag):
            yield ost


def popleft_append(buff, start_idx, it):
    """ This function emulates popleft and append from
        collections.deque all in one step for a circular buffer
        of size n which is always full,
        The argument to parameter "it" must be an infinite 
        generator iterable, otherwise it.next() may throw
        an exception at some point """
    left_popped = buff[start_idx]
    buff[start_idx] = it.next()
    return (start_idx + 1) % len(buff), left_popped


def circular_peek(seq, start_idx):
    return seq[start_idx:len(seq)] + seq[:start_idx]


# Example usage for peek queue of size 5
rg = random_generator()
pqsize = 5
# Initialize buffer using pqsize elements from generator
buff = [rg.next() for _ in xrange(pqsize)]
start_idx = 0
# Game loop
while True:
    # Popping one OST (one-sided tetromino) from queue and 
    # adding a new one, also updating start_idx
    start_idx, left_popped = popleft_append(buff, start_idx, rg)
    # To show the peek queue currently
    print circular_peek(buff, start_idx)
def get_tile():
    from random import shuffle
    tiles = list("IJLOSTZ")
    while True:
        shuffle(tiles)
        for tile in tiles:
            yield tile
class PeekableQueue:
    def __init__(self, item_getter, maxpeek=50):
        self.getter = item_getter
        self.maxpeek = maxpeek
        self.b = [next(item_getter) for _ in range(maxpeek)]
        self.i = 0

    def pop(self):
        result = self.b[self.i]
        self.b[self.i] = next(self.getter)
        self.i += 1
        if self.i >= self.maxpeek:
            self.i = 0
        return result

    def peek(self, n):
        if not 0 <= n <= self.maxpeek:
            raise ValueError("bad peek argument %r" % n)
        nthruend = self.maxpeek - self.i
        if n <= nthruend:
            result = self.b[self.i : self.i + n]
        else:
            result = self.b[self.i:] + self.b[:n - nthruend]
        return result

q = PeekableQueue(get_tile())