Arquivo de Fevereiro de 2009

8 Puzzle - Parte 2

Ontem fiz um programa para resolver o 8 puzzle. Testei em casa com o puzzle presente no jogo Professor Layton e para minha tristeza o programa era lento demais. Deixei ligado por vários minutos e a resposta não apareceu. Logicamente precisava aumentar a performance e a maneira mais óbvia era trocar o algoritmo de busca, já que a busca em largura, apesar de dar a resposta correta, é muito lenta.

Dito isto, troquei para o algoritmo A*, que é o super bambambam dos algoritmos de busca. Para implementar este algoritmo, preciso de uma fila de prioridade e felizmente a biblioteca de Python já possui uma implementação no módulo heapq. Também é preciso associar um custo com cada configuração do tabuleiro e fiz da seguinte forma: o custo de um configuração é a soma da quantidade de movimentos até chegar nela mais a soma das distâncias Manhattan de cada quadrado até sua posição na configuração final. Usei uma matriz bacana para armazenar fácil essas distâncias, hehe!

Por fim, rodei o programa e dentro de poucos minutos ele deu uma resposta. Testei e a resposta está correta. Até que enfim resolvi esse puzzle terrível! O código está abaixo.

Yesterday I wrote a program to solve the 8 puzzle, but my implementation was too slow. I changed the search algorithm to A* and everyting worked just fine. I finally got through that pesky puzzle in the game.


"""
This is a solver for the 8 sliders puzzle.

A state is a snapshot of the game board. It is represented by a list of
9 items, eg. [0,1,2,3,4,5,6,7,8]. The positions in the list map to the
following positions on the board:
        0 1 2
        3 4 5
        6 7 8
We assume the square labelled 0 is the empty square. It is possible
to have more of the same number on the board.
"""

# A Priority Queue implementation
import heapq

""" The allowed moves in the game. For example, 0:[1,3] means that
a square in position zero can be swapped with the square in position one
or the square in position three.
"""
SWAPS = {0:[1,3],
         1:[0,2,4],
         2:[1,5],
         3:[0,4,6],
         4:[1,3,5,7],
         5:[2,4,8],
         6:[3,7],
         7:[4,6,8],
         8:[5,7]}

def solve_eight_puzzle(begin, goal):
    distance = calculate_distance(goal)
    openlist = []
    heapq.heappush(openlist, package_cost(begin, 0, distance))
    closedlist = []
    previous = {begin:None}
    while len(openlist) > 0:
        print len(openlist),
        cost, state = heapq.heappop(openlist)
        closedlist.append(state)
        if state == goal:
            s = goal
            path = []
            while previous[s] is not None:
                path.append(s)
                s = previous[s]
            path.append(s)
            path.reverse()
            return path
        nextstates = [s for s in possible_moves(state) if s not in closedlist]
        for next in nextstates:
            previous[next] = state
            heapq.heappush(openlist, package_cost(next, cost + 1, distance))
    return None

def package_cost(state, path_length, distance):
    cost = path_length
    for i in range(9):
        cost += distance[state[i]][i]
    return (cost, state)

def possible_moves(state):
    izero = index_of_zero(state)
    swaps = SWAPS[izero]
    new_states = []
    for swap in swaps:
        new_state = list(state)
        new_state[izero] = state[swap]
        new_state[swap] = state[izero]
        new_states.append(tuple(new_state))
    return new_states

def instructions(state_list):
    answer = []
    previous = None
    for state in state_list:
        if previous is not None:
            izero1 = index_of_zero(previous)
            izero2 = index_of_zero(state)
            if izero1 / 3 < izero2 / 3:
                # zero went down
                inst = "move a piece up"
            elif izero1 / 3 == izero2 / 3:
                if izero1 < izero2:
                    # zero  went right
                    inst = "move a piece left"
                else:
                    #zero went left
                    inst = "move a piece right"
            elif izero1 / 3 > izero2 / 3:
                # zero went up
                inst = “move a piece down”
            answer.append(inst)
        previous = state
    return answer            

def index_of_zero(tup):
    i = 0
    while i < 9:
        if tup[i] == 0:
            return i
        i += 1
    print "Mega error: there is no ZERO in", tup

def calculate_distance(goal):
    distance = {}
    distance[goal[0]] = [0, 1, 2, 1, 2, 3, 2, 3, 4]
    distance[goal[1]] = [1, 0, 1, 2, 1, 2, 3, 2, 3]
    distance[goal[2]] = [2, 1, 0, 3, 2, 1, 4, 3, 2]
    distance[goal[3]] = [1, 2, 3, 0, 1, 2, 1, 2, 3]
    distance[goal[4]] = [2, 1, 2, 1, 0, 1, 2, 1, 2]
    distance[goal[5]] = [3, 2, 1, 2, 1, 0, 3, 2, 1]
    distance[goal[6]] = [2, 3, 4, 1, 2, 3, 0, 1, 2]
    distance[goal[7]] = [3, 2, 3, 2, 1, 2, 1, 0, 1]
    distance[goal[8]] = [4, 3, 2, 3, 2, 1, 2, 1, 0]
    return distance 

# Test
i = 1
print
for inst in instructions(solve_eight_puzzle((1,2,3,4,5,6,7,0,1),(1,2,3,4,0,7,6,5,1))):
    print i, inst
    i += 1
raw_input()

Adicionar comentário 18 de Fevereiro de 2009 às 12:29 Thiago

8 Puzzle

O problema 8 puzzle é um quebra cabeça em que temos um tabuleiro 3 por 3 com 8 quadrados deslizantes e um espaço em branco. Dada uma posição inicial dos quadrados, pede-se para chegar em uma outra posição qualquer considerando que:

  • Os quadrados podem ser deslizados para ocuparem a posição previamente em branco.
  • Movimentos diagonais são proibidos.
  • As peças só podem ser deslizadas, isto é, não pode-se tirá-las do tabuleiro.

Estava jogando o ótimo Professor Layton and the Curious Village quando emperrei num dos puzzles, que é um 8 puzzle (Puzzle 107: A Worm’s Dream). Depois de milhares de movimentos, desisti e resolvi escrever um programa para resolver o problema para mim, hehe. Apresento a solução abaixo, escrita em Python 2.5.

Adotei a abordagem mais simples, é uma busca em largura sem heurística no grafo onde os vértices são as diversas configurações do tabuleiro. Primeiro implementei a busca como uma função recursiva, mas estava estourando a pilha (Stack Overflow) então reescrevi a função de forma iterativa.

Conseguem ver como ela funciona? O que pode ser feito para melhorar a performance? :)

Alguém tem uma idéia melhor para como armazenar a seqüência de movimentos que o jogador tem que fazer?


This Python 2.5 program solves the 8 puzzle. It uses a simple breadth first search algorithm in a graph where the vertices are game board snapshots.

Can you see how it works? How can the performance be improved? Does someone have a better idea of how the answer should be stored or presented?


"""
This is a solver for the 8 sliders puzzle.

A state is a snapshot of the game board. It is represented by a list of
9 items, eg. [0,1,2,3,4,5,6,7,8]. The positions in the list map to the
following positions on the board:
        0 1 2
        3 4 5
        6 7 8
We assume the square labelled 0 is the empty square. It is possible
to have more of the same number on the board.
"""

""" The allowed moves in the game. For example, 0:[1,3] means that
a square in position zero can be swapped with the square in position one
or the square in position three.
"""
SWAPS = {0:[1,3],
         1:[0,2,4],
         2:[1,5],
         3:[0,4,6],
         4:[1,3,5,7],
         5:[2,4,8],
         6:[3,7],
         7:[4,6,8],
         8:[5,7]}

def solve_eight_puzzle(begin, goal):
    openlist = [begin]
    closedlist = []
    previous = {tuple(begin):None}
    while len(openlist) > 0:
        state = openlist.pop(0)
        closedlist.append(state)
        if state == goal:
            s = tuple(goal)
            path = []
            while previous[s] is not None:
                path.append(s)
                s = previous[s]
            path.append(s)
            path.reverse()
            return path
        nextstates = [s for s in possible_moves(state) if s not in closedlist]
        for next in nextstates:
            previous[tuple(next)] = tuple(state)
        openlist.extend(nextstates)
    return None

def possible_moves(state):
    izero = state.index(0)
    swaps = SWAPS[izero]
    new_states = []
    for swap in swaps:
        new_state = state[:]
        new_state[izero] = state[swap]
        new_state[swap] = state[izero]
        new_states.append(new_state)
    return new_states

# Test
print solve_eight_puzzle([0,1,3,4,2,5,7,8,6],[1,2,3,4,5,6,7,8,0])

Adicionar comentário 17 de Fevereiro de 2009 às 17:27 Thiago

Top Games

Farei aqui, agora, uma lista pessoal dos (aproximadamente) 7 melhores jogos que já joguei em cada hardware:

Atari

  1. Adventure
  2. Hero
  3. River Raid
  4. Keystone Keepers
  5. Enduro
  6. Frogger
  7. Q*bert

MSX

  1. Knightmare
  2. Penguin Adventure
  3. Magical Tree
  4. Payload
  5. The Castle
  6. Nemesis
  7. Atlantis

Master System

  1. Phantasy Star
  2. Psycho Fox
  3. Land of Illusion
  4. Alex Kidd in Miracle World
  5. Sonic
  6. California Games
  7. Lemmings

Mega Drive

  1. Phantasy Star 4
  2. Super Street Fighter 2
  3. Beyond Oasis
  4. Road Rash 2
  5. Sonic 3 & Knuckles
  6. Shinobi 3
  7. Jungle Strike
  8. Landstalker
  9. Flashback
  10. ToeJam & Earl

Saturn

  1. Guardian Heroes
  2. Dragon Force
  3. X-Men vs Street Fighter
  4. Virtual On
  5. Mr. Bones
  6. Panzer Dragoon Saga
  7. Radiant Silvergun
  8. Burning Rangers
  9. Nights into Dreams

Dreamcast

  1. Soul Calibur
  2. Shenmue
  3. Rez
  4. Jet Grind Radio
  5. Power Stone 2
  6. Crazy Taxi
  7. Skies of Arcadia

PC

  1. Planescape:Torment
  2. Fallout
  3. Baldur’s Gate 2
  4. Starcraft
  5. Duke Nukem 3D
  6. Grim Fandango
  7. Total Annihilation

1 comentário às 14:25 Thiago

Pirataria e o mercado de jogos

Têm-se falado muito sobre como a pirataria está afetando o mercado de jogos. Grandes produtoras estão procurando investir mais em consoles do que em PCs devido às proteções que existem.

As técnicas de proteção contra cópia em jogos acabam prejudicando justamente os compradores de jogos, pois sofrem com incompatibilidade, lentidão, com a incoveniência de ter que deixar o CD do jogo no drive, etc, coisa pela qual os usuários de jogos piratas não têm que passar. Mas vejam o experimento com World of Goo, jogo premiadíssimo, produzido por 2 pessoas. Este jogo foi lançado sem qualquer proteção contra cópia, como uma maneira dos autores analisarem se a existência de proteção influencia o nível de pirataria ou não. Descobriram que não, pois mesmo o jogo sendo barato, 85% dos jogadores usam cópias piratas.

A produção de jogos consome dinheiro. Se não ha incentivo financeiro para criar jogos, logicamente não haverá mais tantos jogos (seria uma nova bolha?).

Eu passei a acreditar que a única maneira de reduzir a pirataria é conscientizar os jogadores a comprarem jogos originais. Ao menos, aqueles que eles gostaram de verdade.

Obs: Isso não se aplica a MMORPGs, mas não é todo mundo que gosta deles.

1 comentário 13 de Fevereiro de 2009 às 08:17 Thiago


Calendário

Fevereiro 2009
S T Q Q S S D
« Dez   Mar »
 1
2345678
9101112131415
16171819202122
232425262728  

Minhas Publicações Recentes

Publicações por Mês

Estatísticas

Meta