sexta-feira, 19 de dezembro de 2014

Teste #3 - TP 1

P1

Explicar com rigor o que se passou na listagem abaixo.
>>> lista = [[]] * 3
>>> lista
[[], [], []]
>>> lista[1].append('ai')
>>> lista
[['ai'], ['ai'], [‘ai']]
A primeira instrução vai criar uma lista com três elementos, todos eles uma lista vazia, que de é visualizada(linha 2). Aplicamos depois o método append para acrescentar à lista vazia na segunda posição a cadeia de caracteres 'ai'. O método append altera a lista na segunda posição sem alterar a sua localização. Como os três elementos iniciais partilham a mesma memória, isto é, todos apontam para o mesmo objecto (a lista vazia), alterar um deles significa alterar todos. Estamos perante uma das consequências das listas serem objectos mutáveis e haver partilha de memória.

P2

Um ficheiro com a seguinte informação por linha: ano e cotação. Qual a cotação máxima e o respectivo ano?
def cota_maxima(ficheiro):
    with open(ficheiro) as f_ent:
        maximo = 0
        ano = 0
        for linha in f_ent:
            dados = linha[:-1].split()
            ano_cur = int(dados[0])
            valor_cota = float(dados[1])
            if valor_cota > maximo:
                maximo = valor_cota
                ano = ano_cur
        return ano, maximo
Erro mais frequente: ler todas as linhas com o readlines e depois aplicar o método split. Só que o readlines devolve uma lista e o split não se aplica a listas.

P3

A classificação do campeonato está guardada num dicionário (chave = nome clube, valor = pontos). Conhecidos os resultados de uma jornada, actualizar a classificação. Os resultados da jornada é representado por uma lista de jogos e cada jogo é uma lista em que o primeiro elemento é o nome do clube 1, o segundo os golos marcado, o terceiro o nome do clube 2 e o quarto os golos marcados.

def actualiza_tabela(tabela, resultados):
    for resultado in resultados:
        if resultado[1] > resultado[3]:
            tabela[resultado[0]] += 3
        elif resultado[1] == resultado[3]:
            tabela[resultado[0]] += 1
            tabela[resultado[2]] += 1
        else:
            tabela[resultado[2]] += 3
    return tabela
Erro comum: usar dois ciclos em que o primeiro percorre o dicionário e o segundo repete quatro vezes a alteração de um jogo…

segunda-feira, 8 de dezembro de 2014

A minha lista telefónica

É tempo de desenvolver uma aplicação interactiva que permita manter actualizada a minha lista de contactos telefónicos. Para simplificar vamos admitir que a lista apenas vai conter o nome e o número. Em abstracto existem várias operações que vou pretender poder fazer: adicionar um número, remover um número, procurar um número, carregar a lista de contactos, salvar um lista de contactos.

Sendo uma lista de contactos telefónicos que quero manter permanentemente, é óbvio que ela vai estar guardada num ficheiro. Por outro lado, a actualização da lista vai obrigar a operações que me interessa que sejam feitas de forma simples e eficiente. Veremos que um modo de o conseguir é ter os contactos armazenados na memória como um dicionário, de chave igual ao nome e valor igual ao número de telefone.

. Este tipo de programas interactivos tem uma estrutura clássica, ou se quiserem, existe um padrão de desenho para o resolver. Fundamentalmente teremos um ciclo potencialmente infinito que em cada repetição imprime um menu de opções e depois, em função das nossas escolhas lança o respectivo programa. Mostremos então esse padrão.
def main():
    lista_tele = {}
    while True:
        mostra_menu()
        menu_escolhas = int(input("Escolha a sua opção (1-7): "))
        if menu_escolhas == 1:
            mostra_lista(lista_tele)
        elif menu_escolhas == 2:
            print("Acrescentar Nome e Número")
            nome = input("Nome: ")
            telefone = input("Número: ")
            acrescenta_numero(lista_tele, nome, telefone)
        elif menu_escolhas == 3:
            print("Retira Nome e Número")
            nome = input("Nome: ")
            retira_contacto(lista_tele, nome)
        elif menu_escolhas == 4:
            print("Procura Número")
            nome = input("Nome: ")
            print(procura_numero(lista_tele, nome))
        elif menu_escolhas == 5:
            nome_ficheiro = input("Ficheiro a carregar: ")
            carrega_lista(lista_tele, nome_ficheiro)
        elif menu_escolhas == 6:
            nome_ficheiro = input("Ficheiro a salvar: ")
            salva_lista(lista_tele, nome_ficheiro)
        elif menu_escolhas == 7:
            break
        else:
            mostra_menu()
     
    print("Fim")
    
def mostra_menu():
    pass

def mostra_lista(lista_tele):
    pass

def acrescenta_numero(lista_tele,nome,telefone):
    pass
    
def retira_contacto(lista_tele, nome):
    pass

def procura_numero(lista_tele,nome):
    pass

def carrega_lista(lista_tele, nome_ficheiro):
    pass

def salva_lista(lista_tele,nome_ficheiro):
    pass

if __name__ == '__main__':
    main()

Este programa já pode ser corrido, para testes primitivos, embora não faça nada ainda de relevante. Vamos então completar o que falta, começando pelo mais simples, a função que mostra o menu de opções.
def mostra_menu():
    print("1. Mostra Lista Telefónica")
    print("2. Acrescentar Entrada (Nome, Número)")
    print("3. Retirar Entrada (Nome, Número)")
    print("4. Procurar Número")
    print("5. Carregar Lista Telefónica")
    print("6. Salvar Lista Telefónica")
    print("7. Terminar")
    print()
Vamos agora passar às funções mais interessantes e que se baseiam nas escolhas já enunciadas: a lista está guardada num ficheiro externo e é manipulada internamente como um dicionário. Carregar a lista telefónica consiste então em ler um ficheiro e guardar essa informação num dicionário.
def carrega_lista(lista_tele, nome_ficheiro):
    with open(nome_ficheiro,'r') as f_ent:
        for contacto in f_ent:
            dados = contacto[:-1]
            nome, numero = dados.split()
            lista_tele[nome] = numero
        return lista_tele
Este programa pode ser testado de forma autónoma, bastando apenas existir previamente um pequeno ficheiro com uma lista telefónica. Por exemplo, com o ficheiro
ernesto   123
patricia  456
daniela 789
pedro 012
ao corremos o programa o resultado será:
{'pedro': '012', 'ernesto': '123', 'patricia': '456', 'daniela': '789'}
Notar que os nomes e os números são tratados como cadeia de caracteres. Vamos agora escrever o código da função inversa, ou seja, salvar uma lista telefónica interna guardada num dicionário num ficheiro.
def salva_lista(lista_tele,nome_ficheiro):
    with open(nome_ficheiro,'w') as f_saida:
        for chave,valor in lista_tele.items():
            contacto = chave + '  ' + valor + '\n'
            f_saida.write(contacto)
Não há muitas observações a fazer. Apenas notar que usamos espaços em branco para separar o nome do número e que cada contacto termina com a marca de fim de linha. Vamos agora implementar as funções que internamente manipulam a lista telefónica.
def acrescenta_numero(lista_tele,nome,telefone):
    lista_tele[nome] = telefone
    return lista_tele
    
def retira_contacto(lista_tele, nome):
    if nome in lista_tele:
        del lista_tele[nome]
        return lista_tele
    else:
        print('Contacto inexistente')

def procura_numero(lista_tele,nome):
    if nome in lista_tele:
        print(lista_tele[nome])
    else:
        print('Contacto inexistente’)
São programas muito simples. Apenas temos o cuidado de verificar que o contacto existe na lista. Fica por resolver a função que mostra a lista telefónica. Também não apresenta grande dificuldade. O único aspecto que requere a nossa atenção é que a visualização seja feita por ordem dos nomes.
def mostra_lista(lista_tele):
    for nome,numero in sorted(lista_tele.items()):
        print()
        print('Nome: %s\nNúmero: %s' % (nome,numero))
E pronto. Agora pode divertir-se. Mas lembre-se que se trata de uma solução simples e com algumas fragilidades. Em particular, não esta muito protegida contra erros na entrada de dados, ou não está previsto que uma pessoa possa ter mais do que um telefone. Pense nestas e noutras questões e melhore a solução apresentada.

sábado, 6 de dezembro de 2014

Do Problema Ao Programa

Programar é uma actividade complexa. Obriga-nos a dominar o domínio do problema (se eu não souber o que são números primos como posso escrever um programa para verificar se um número é primo?), obriga-nos a conhecer uma linguagem de programação (se eu precisar de ler informação guardada num ficheiro externo e não conhecer as instruções sobre ficheiros, como posso esperar resolver o problema?). Mas mesmo tendo estas duas competências, ainda é preciso outra, fundamental: como passar do problema ao programa, como dominar a complexidade inerente ao desenvolvimento de um programa? Vamos ver um dos modos de dominar essa complexidade, que consiste em decompor um problema em sub-problemas mais simples, algo que Descartes enunciou de modo claro.

Vamos supor que o nosso problema consiste em querer desenhar numa tela um caminho aleatório formado por segmentos de recta. Vamos supor ainda que nos pedem que fique guardado num ficheiro externo uma representação do caminho. Vamos então proceder por partes.
def caminho_alea(n):
    # Gerar segmentos do caminho
    # Guardar caminho
    # Ler caminho
    # Mostrar caminho
    pass

if __name__ == '__main__':
    caminho_alea(30)
O que nos diz esta solução? Bom , diz-nos que decidimos ter n segmentos, que vão ser gerados, depois guardados, a seguir lidos e finalmente desenhados. Ou seja: dividimos o nosso problema inicial em quatro sub-problemas. O curioso da questão é que este programa corre sem erros, embora não faça nada…

Como vamos resolver cada sub-problema? Por que ordem? Vamos começar por resolver o primeiro dos quatro. Vamos tornar mais específico o enunciado. Vamos admitir que um segmento é representado pelos seus pontos extremos. Vamos ainda concretizar os valores possíveis para dada um dos pontos. Como se trata de um caminho, vamos garantir a ligação entre os segmentos impondo que os segmentos consecutivos têm uma extremidade em comum. Isso tem como consequência que eu possa pensar em termos de pontos consecutivos e não em segmentos. Assim preciso de um programa que gere pontos, podendo cada um deles assumir valores num dado intervalo. Os pontos serão representados por uma lista com as coordenadas do ponto.
import random

def gera_pontos(n, inf, sup):
    pontos = []
    for i in range(n):
        pontos.append([random.randint(inf,sup), random.randint(inf,sup)])
    return pontos

def gera_pontos_2(n,inf,sup):
    return [[random.randint(inf,sup), random.randint(inf,sup)] for i in range(n)]
Como se pode ver, apresentamos duas soluções alternativas, uma primeira convencional e, uma segunda, que recorre a listas por compreensão. Em qualquer das soluções usamos listas como contentores onde são guardados os pontos. Os valores são gerados aleatoriamente recorrendo ao módulo random. Com este sub-problema resolvido, e que pode ser testado isoladamente, podemos avançar no nosso programa.
def caminho_alea(n, inf,sup):
    # Gerar segmentos do caminho
    pontos = gera_pontos(n, inf, sup)
    # Guardar caminho
    # Ler caminho
    # Mostrar caminho
Note-se que o número de parâmetros foi alterado e também o significado do primeiro. Agora não traduz o número de segmentos mas antes o número de pontos. Continuemos. Vamos guardar os pontos num ficheiro externo, um ponto por linha.
def guarda_caminho(ficheiro, dados):
    fich_dados = open(ficheiro,'w')
    for x,y in dados:
        linha = str(x) + '\t' + str(y) + '\n'
        fich_dados.write(linha)
    fich_dados.close()   
Esta solução não tem nada de especial: abrimos um ficheiro para escrita (criando-o caso não exista), e depois vamos percorrer a lista de pontos retirando-os um a um, fabricando a cadeia de caracteres que representa a linha e escrevendo essa linha no ficheiro. Notar que existe uma tabulação entre cada coordenada e que a linha termina com a marca de fim de linha. Este programa pode ser testado isoladamente! Por outro lado, sendo autónomo podemos usá-lo noutro tipo de aplicação. A modularidade compensa!! E de novo a nossa solução global pode ser refinada.
def caminho_alea(n, inf,sup, ficheiro):
    # Gerar segmentos do caminho
    pontos = gera_pontos(n, inf, sup)
    # Guardar caminho
    guarda_caminho(ficheiro,pontos)
    # Ler caminho
    # Mostrar caminho
Notar o aparecimento de mais um parâmetro, o nome do ficheiro. Resolvida esta questão vamos passar à operação inversa: obter as coordenadas a partir do ficheiro.
def ler_caminho(ficheiro):
    fich_dados = open(ficheiro,'r')
    valores = []
    for linha in fich_dados:
        ponto = linha[:-1].split('\t')
        x = int(ponto[0])
        y = int(ponto[1])
        valores.append([x,y])
    fich_dados.close() 
    return valores
O que se pode dizer sobre esta solução? Talvez chamar a atenção para o modo como se transformou uma cadeia de caracteres que representa uma linha do ficheiro, numa lista com dois inteiros. linha[:-1] retira a marca de fim de linha. split(’\t’) divide o que resta numa lista com duas cadeias de caracteres que representam as coordenadas. Como são cadeias de caracteres foi preciso converter cada uma num inteiro. E nova solução global.
def caminho_alea(n, inf,sup, ficheiro):
    # Gerar segmentos do caminho
    pontos = gera_pontos(n, inf, sup)
    # Guardar caminho
    guarda_caminho(ficheiro,pontos)
    # Ler caminho
    pontos = ler_caminho(ficheiro)
    # Mostrar caminho
Já só nos falta mostrar o caminho. Vamos fazê-lo recorrendo ao módulo matplotlib.
import matplotlib.pyplot as plt

def mostra_caminho(pontos):
    plt.title('Caminho')
    plt.xlabel('X')
    plt.ylabel('Y')
    lista_x = [ x for x,y in pontos]
    lista_y = [y for x,y in pontos]
    plt.plot(lista_x,lista_y)
    plt.show()   
Nesta solução, que uma vez mais podemos testar isoladamente, a única dificuldade residia no facto de termos que separar os pontos em duas listas, cada uma com os valores de cada coordenada. Para os que gostam de aventuras mais arrojadas, deixamos aqui uma alternativa à conversão dos dados que recorre ao iterador zip.
import matplotlib.pyplot as plt

def mostra_caminho(pontos):
    plt.title('Caminho')
    plt.xlabel('X')
    plt.ylabel('Y')
    coord = zip(*pontos)
    plt.plot(*coord)
    plt.show()  
Um dia explicaremos o porquê do asterisco... E agora vamos juntar tudo.
def caminho_alea(n, inf,sup, ficheiro):
    # Gerar segmentos do caminho
    pontos = gera_pontos(n, inf, sup)
    # Guardar caminho
    guarda_caminho(ficheiro,pontos)
    # Ler caminho
    pontos = ler_caminho(ficheiro)
    # Mostrar caminho
    mostra_caminho(pontos)
E pronto. Já está. Ou talvez não… Olhando para a solução acima é um pouco bizarro que tendo os pontos logo após o primeiro passo, vamos estar a lê-los a partir do ficheiro. Daí se possa simplificar o programa.
def caminho_alea(n, inf,sup, ficheiro):
    # Gerar segmentos do caminho
    pontos = gera_pontos(n, inf, sup)
    # Guardar caminho
    guarda_caminho(ficheiro,pontos)
    # Mostrar caminho
    mostra_caminho(pontos)
Agora sim: geramos, guardamos e mostramos! Moral da estória: dividir compensa!

domingo, 23 de novembro de 2014

Jogo do Galo: um exercício em programação descendente

Todos conhecemos o Jogo do Galo (que os ingleses chamam de Tic-Tac-Toe). O objectivo deste post é mostrar como podemos desenvolver um programa simples para permitir um humano jogar contra o computador. Mas o nosso interesse não é tanto no jogo, mas mais em ilustrar como podemos chegar à versão final, funcional, do programa de forma segura, tomando a cada momento as decisões mínimas que nos ajudem na sua construção. Chama-se a isso Construção Descendente de Programas. Vamos a isso.

Existem muitos aspectos a considerar no problema, mas se esquecermos os detalhes podemos propor uma primeira versão do nosso programa:

def tic_tac_toe_0(n):
    # inicializa tabuleiro
    # mostra tabuleiro
    # define jogador
    while True : # não há vencedor ou empate
        if True: # humano
            # pede jogada
            # actualiza tabuleiro
            pass
        else:
            # define jogada
            # actualiza tabuleiro
            pass
        # mostra tabuleiro
        # Vencedor ou empate --> termina
        # comuta jogador
    # mensagem final
Como se pode ver o jogo começa essencialmente por construir um tabuleiro e definir quem é o jogador que começa. Depois entramos num ciclo em que os jogadores jogam alternadamente enquanto não houver vencedor ou o jogo estar empatado. Podemos avançar um pouco começando por definir as partes que envolvem criar um tabuleiro e mostrar o tabuleiro.
def tic_tac_toe_1(n):
    # inicializa tabuleiro
    tabuleiro = inicializa_tabuleiro(n)
    # mostra tabuleiro
    mostra_tabuleiro(tabuleiro)
    # define jogador
    while True : # não há vencedor ou empate
        if True: # humano
            # pede jogada
            # actualiza tabuleiro
            pass
        else:
            # define jogada
            # actualiza tabuleiro
            pass
        # mostra tabuleiro
        mostra_tabuleiro(tabuleiro)
        # Vencedor ou empate --> termina
        # comuta jogador
    # mensagem final
Agora precisamos definir as novas funções. Para isso, é necessário decidir a representação para o tabuleiro. Optamos por uma lista de listas. Assim podemos facilmente modificar o seu conteúdo. Cada célula do tabuleiro, um elemento da lista de listas, poderá ter um de três valores: 0, quando está vazia, ‘X’ ou ‘O’ para a marca de cada jogador.

Começando pela inicialização do tabuleiro, podemos optar pela solução trivial:
def inicializa_tabuleiro_0(n):
    tabuleiro = []
    for i in range(n):
        # linha i
        linha_i = []
        for j in range(n):
            linha_i += [0]
        tabuleiro += [linha_i]
    return tabuleiro
Dois ciclos. O externo trata das linhas, o interno das colunas para cada linha. Uma alternativa:
def inicializa_tabuleiro(n):
    tabuleiro = []
    for i in range(n):
        # linha i
        tabuleiro += [[0] * n]
    return tabuleiro
Quem já conhece as listas por compreensão poderia optar por:
def inicializa_tabuleiro(n):
    return [[0] * n for i in range(n)]
Agora mostrar o tabuleiro. A solução banal:
def mostra_tabuleiro(tabuleiro):
    print(tabuleiro)
Esta solução esquece a natureza bi-dimensional do tabuleiro. Mas isso pode-se resolver, ainda de modo simples:
def mostra_tabuleiro(tabuleiro):
    for linha in tabuleiro:
        for valor in linha:
            print(valor,'  ',end='')
        print()
E agora? Que fazer? Vamos definir o jogador:
def define_jogador():
    import random
    return random.choice(‘XO')
Notar onde é feita a importação do módulo. Isso tem consequências. Sabe quais são, certo? Com este modo de proceder, avançamos para uma versão mais detalhada:
def tic_tac_toe_2(n):
    # inicializa tabuleiro
    tabuleiro = inicializa_tabuleiro(n)
    # mostra tabuleiro
    mostra_tabuleiro(tabuleiro)
    # define jogador
    jogador = define_jogador()
    while True : # não há vencedor ou empate
        if jogador == 'X': # humano
            # pede jogada
            # actualiza tabuleiro
            pass
        else:
            # define jogada
            # actualiza tabuleiro
            pass
        # mostra tabuleiro
        mostra_tabuleiro(tabuleiro)
        # Vencedor ou empate --> termina
        # comuta jogador
        if jogador == 'X':
            return 'O'
        else:
            return 'X'        
    # mensagem final 
Está na hora de resolver o problema do conceito de jogada. Vamos admitir que jogar é simplesmente indicar as coordenadas da célula onde se pretende jogar. Existe uma diferença entre ser o humano ou ser o computador. No primeiro caso o programa dever pedir ao humano essas coordenadas:
def pede_jogada(tabuleiro):
    jogada = eval(input('A sua jogada (x,y)?: '))
    while not possivel(tabuleiro,jogada):
        print('Jogada ilegal...')
        jogada = eval(input('A sua jogada (x,y)?: '))
    return jogada


def possivel(tabuleiro,jogada):
    return tabuleiro[jogada[0]][jogada[1]] == 0
Só estamos a proteger o programa contra tentavas de jogar numa célula já ocupada…. No caso do computador a jogar podemos definir definir diferentes estratégias. apresentamos duas: numa, o computador procura por uma certa ordem a primeira posição disponível; na outra, o computador escolhe aleatoriamente uma de entre as disponíveis.
def joga_computador(tabuleiro):
    """Escolha por ordem"""
    for i in range(len(tabuleiro)):
        for j in range(len(tabuleiro[0])):
            if tabuleiro[i][j] == 0:
                return i,j
            
def joga_computador_b(tabuleiro):
    import random
    """Escolha aleatória"""
    livres = []
    for i in range(len(tabuleiro)):
        for j in range(len(tabuleiro[0])):
            if tabuleiro[i][j] == 0:
                livres += [(i,j)]
    return random.choice(livres)
Daqui resulta uma nova aproximação.
def tic_tac_toe_3(n):
    # inicializa tabuleiro
    tabuleiro = inicializa_tabuleiro(n)
    # mostra tabuleiro
    mostra_tabuleiro(tabuleiro)
    # define jogador X (humano)  ou O (computador)
    jogador = define_jogador()
    while True: # não há vencedor ou empate
        if jogador == 'X': # humano
            print('Joga Humano')
            # pede jogada
            jogada = pede_jogada(tabuleiro)
            # actualiza tabuleiro
        else:
            print('Joga computador')
            # define jogada
            jogada = joga_computador_b(tabuleiro)
            # actualiza tabuleiro
        # mostra tabuleiro
        mostra_tabuleiro(tabuleiro)
        # Vencedor ou empate --> termina
        # comuta jogador
        jogador = comuta_jogador(jogador)
    # mensagem final
Com o conceito de jogada definido podemos passar à actualização do tabuleiro. Como temos que colocar marcas diferentes, a função vai ter três parâmetros: o tabuleiro, a jogada (a posição) e a marca:
def actualiza_tabuleiro(tabuleiro, jogada, jogador):
    if jogador == 'X':
        tabuleiro[jogada[0]][jogada[1]] = 'X'
    else:
        tabuleiro[jogada[0]][jogada[1]] = 'O'
    return tabuleiro
E aproximamo-nos do final:
def tic_tac_toe_4(n):
    # inicializa tabuleiro
    tabuleiro = inicializa_tabuleiro(n)
    # mostra tabuleiro
    mostra_tabuleiro(tabuleiro)
    # define jogador X (humano)  ou O (computador)
    jogador = define_jogador()
    while True: # não há vencedor ou empate
        if jogador == 'X': # humano
            print('Joga Humano')
            # pede jogada
            jogada = pede_jogada(tabuleiro)
            # actualiza tabuleiro
            tabuleiro = actualiza_tabuleiro(tabuleiro,jogada,jogador)
        else:
            print('Joga computador')
            # define jogada
            jogada = joga_computador_b(tabuleiro)
            # actualiza tabuleiro
            tabuleiro = actualiza_tabuleiro(tabuleiro,jogada,jogador)
        # mostra tabuleiro
        mostra_tabuleiro(tabuleiro)
        # Vencedor ou empate --> termina
        # comuta jogador
        jogador = comuta_jogador(jogador)
    # mensagem final
Vamos tratar agora de uma parte mais difícil: Determinar se há vencedor ou se o jogo está empatado. Comecemos pela primeira questão. Haverá vencedor se um jogador tiver N marcas consecutivas, numa linha ou numa coluna ou numa diagonal. Vamos então separar estas três verificações, parando mal uma delas nos permita tirar conclusões. Então:
def vencedor(tabuleiro,jogador):
    return vence_linhas(tabuleiro,jogador) or vence_colunas(tabuleiro, jogador) or vence_diagonais(tabuleiro, jogador)

def vence_linhas(tabuleiro,jogador):
    for i in range(len(tabuleiro)):
        vence = True
        for j in range(len(tabuleiro[0])):
            vence = vence and (tabuleiro[i][j] == jogador)
        if vence:
            return True
    return False
        
def vence_colunas(tabuleiro,jogador):
    for i in range(len(tabuleiro[0])):
        vence = True
        for j in range(len(tabuleiro)):
            vence = vence and (tabuleiro[j][i] == jogador)
        if vence:
            return True
    return False

def vence_diagonais(tabuleiro,jogador):
    vence_1 = True
    vence_2 = True
    # diagonal principal
    for i in range(len(tabuleiro)):
        vence_1 = vence_1 and (tabuleiro[i][i] == jogador)
        vence_2 = vence_2 and (tabuleiro[i][len(tabuleiro)-1-i] == jogador)
    return vence_1 or vence_2
A verificaão de empate é mais simples:
def empate(tabuleiro):
    for i in range(len(tabuleiro)):
        for j in range(len(tabuleiro[0])):
            if tabuleiro[i][j] == 0:
                return False
    return True
Tome atenção sobretudo ao caso das diagonais e ao modo como calculamos as respectivas posições. Para concluir, só nos falta a mensagem final. Vamos colocá-la no programa e mostar a versão final.
def tic_tac_toe(n):
    # inicializa tabuleiro
    tabuleiro = inicializa_tabuleiro(n)
    # mostra tabuleiro
    mostra_tabuleiro(tabuleiro)
    # define jogador X (humano)  ou O (computador)
    jogador = define_jogador()
    while True: # não há vencedor ou empate
        if jogador == 'X': # humano
            print('Joga Humano')
            # pede jogada
            jogada = pede_jogada(tabuleiro)
            # actualiza tabuleiro
            tabuleiro = actualiza_tabuleiro(tabuleiro,jogada,jogador)
        else:
            print('Joga computador')
            # define jogada
            jogada = joga_computador_b(tabuleiro)
            # actualiza tabuleiro
            tabuleiro = actualiza_tabuleiro(tabuleiro,jogada,jogador)
        # mostra tabuleiro
        mostra_tabuleiro(tabuleiro)
        # Vencedor ou empate --> termina
        if vencedor(tabuleiro,jogador) or empate(tabuleiro):
            break
        # comuta jogador
        jogador = comuta_jogador(jogador)
    # mensagem final
    print('Finito...')
    if empate(tabuleiro):
        print('Fica para o próximo jogo!')
    elif jogador == 'X':
        print('Parabéns humanóide!')
    else:
        print('Parabéns Computador')
Agora já só falta … jogar. Ou quase. Se quiser melhorar o desempenho do computador, tem que pensar numa estratégia diferente O programa seguinte mostra uma hipótese. Não quer completar??
def joga_computador_c(tabuleiro):
    if pode_ganhar_computador(tabuleiro):
        # joga para aí
        pass
    elif pode_ganhar_humano(tabuleiro):
        # bloqueia
        pass
    elif livre_centro():
        # joga centro
        pass
    elif livre_canto():
        # joga_canto
        pass
    else:
        # joga
        pass
Moral da história: devagar se vai ao longe ou a complexidade domina-se dividindo um problema em sub-problemas mais simples.

sábado, 22 de novembro de 2014

Teste 2 - TP1

P1

Que variantes da instrução de atribuição usual ( = ) conhece? Resposta:

Existem três tipos de variantes para a forma explícita da instrução de atribuição: (a) aumentada: op= que equivale a = op por exemplo: x += 5

(b) em cadeia: x = y = … = que equivale a y = ; x = y para o caso de apenas dpis nomes. Exemplo: x = y = 7

(c) multipla: x, y = , , que equivale a x = ; y = , para o caso de apenas dois nomes. Exemplo: x,y = 4,7



P2

Calcular o valor do seno de um ângulo com uma dada precisão. Apresentar ainda o número de termos usados para obter a precisão pedida. O programa deve recorrer à definição de seno que usa a série infinita.

Este problema tem então dois dados de entrada (o ângulo e a precisão) e dois de saída (o seno e o número de termos). Como nos vamos basear na precisão temos que recorrer a um ciclo while. A garantia de cumprir a precisão desejada é controlada pela diferença do valor de duas somas parciais consecutivas.
import math

def seno(x,prec):
    """ Cálculo do seno com uma dada precisão."""
    res = 0
    dif = 1
    exp = 1
    while dif > prec:
        aux = res
        res += (-1)**(exp -1) * (x**(2*exp - 1) / math.factorial(2**exp - 1))
        dif = abs(res - aux)
        exp += 1
    return res, exp
P3

Dadas duas imagens a preto e branco, representadas por um tuplo de tuplos de uns e de zeros, verifique se são o negativo uma da outra. Assegure-se que as imagens têm o mesmo tamanho, caso contrário deve ser desencadeado um erro.

Para controlar o tamanho usamos a instrução assert, que analisa se têm o mesmo número de linhas e de colunas. Depois temos dois ciclos imbricados e mal se encontrem dois valores na mesma posição das duas imagens iguais podemos abandonar pois já sabemos que não podem ser o negativo uma da outra. Lembrar que apenas existem dois valores: 0 e 1.

def negativo(img_1, img_2):
    """ 
    Verifica se duas imagens são o negativo uma da outra.Verifica as dimensões.
    """ 
    assert (len(img_1) == len(img_2)) and (len(img_1[0]) == len(img_2[0])), “ Erro”
    linhas = len(img_1)
    colunas = len(img_1[0])
    for l in range(linhas):
        for c in range(colunas):
            if img_1[l][c] == img_2[l][c]):
                return False
    return True
   

sexta-feira, 14 de novembro de 2014

O passeio da tartaruga

Na última aula vimos como se podia desenhar uma grelha usando turtle. Depois decidimos tornar o problema mais complexo colocando uma tartaruga a passear de modo aleatório nessa grelha. Partindo do centro da grelha a tartaruga a cada momento escolhe um de quatro movimentos (Norte, Sul, Este e Oeste). O mundo é finito pelo que a tartaruga não pode sair da grelha e isso tem que ser controlado pelo programa. Vamos ver uma solução possível.

Primeiro a grelha.

import turtle

def grelha(dim,lado):
    """Desenha uma grelha dim x dim em que cada célula tem de lado lado."""
    turtle.color("gray")
    tam = (dim*lado)
    x = -tam//2
    y = tam//2
    turtle.penup()
    turtle.goto(x,y)
    for lin in range(dim):  
        # Desenha linha de quadrados
        for col in range(dim):
            turtle.pendown()
            quadrado(lado)            
            turtle.penup()
            turtle.setx(turtle.xcor() + lado)
        # reposiciona
        turtle.penup()
        turtle.setposition(x, turtle.ycor()-lado)        
    turtle.hideturtle()

def quadrado(lado):
    for i in range(4):
        turtle.fd(lado)
        turtle.rt(90)

Notar que definimos uma função à parte para desenhar um quadrado, na posição em que a tartaruga se encontrar. Essencialmente, o programa é definido por dois ciclos. O mais exterior controla as linhas, o mais interior as colunas. Verifique também como se define a primeira posição da grela (canto superior esquerdo).

Feito isto, a parte fácil, vamos ao passeio.

def passeio(dim, lado, passos):    
    # Prepara grelha
    turtle.speed(0)
    grelha(dim,lado)
    turtle.color('red')
    turtle.home()
    turtle.pendown()
    # Passeio
    turtle.speed(6)
    turtle.dot()
    turtle.showturtle()
    lim_x = lim_y = (dim*lado)//2
    cor_x = 0
    cor_y = 0
    for i in range(passos):
        vai_para = random.choice(['N','E','S','W'])
        if (vai_para == 'N') and (cor_y < lim_y):
            cor_y += lado
            turtle.setheading(90)
            turtle.fd(lado)
        elif (vai_para == 'E') and (cor_x < lim_x):
            cor_x += lado
            turtle.setheading(0)
            turtle.fd(lado)
        elif (vai_para == 'S') and (cor_y > -lim_y):
            cor_y -= lado
            turtle.setheading(270)
            turtle.fd(lado)
        elif (vai_para == 'W') and (cor_x > -lim_x):
            cor_x -= lado
            turtle.setheading(180)
            turtle.fd(lado) 
        else:
            print((vai_para,turtle.xcor(),turtle.ycor()))
            continue
O programa começa por desenhar (a alta velocidade!!) a grelha e coloca a tartaruga no centro da grelha. De seguida temos um ciclo que executa um número de movimentos dado como parâmetro (passos). Usamos o módulo random para poder escolher de modo aleatório o movimento. Escolhido este, temos um longo if que faz uma análise por casos. Notar que no teste controlamos se a posição escolhida está ou não dentro da grelha. No caso de o movimento não ser possível imprime uma mensagem (ramo else final.). Agora é só experimentar!

quarta-feira, 22 de outubro de 2014

Teste 1 - TP2

Teste 1 - TP2

P1

Os programas são construídos e são executados. Quando os desenvolvemos através de um def eles podem ter argumentos que são nomes. Aos argumentos que aparecem nas definições chamamos parâmetros formais. Quando executamos um programa recorremos ao nome do programa colocando entre parênteses expressões que quando avaliadas correspondem a objectos. Essas expressões são designadas por parâmetros reais.
def xpto(x,y):
 return 2*x*y

print(xpto(4,8))
No exemplo a acima, x e y são parâmetros formais, enquanto 4 e 5 são parâmetros reais. P2

Determinar se a distância entre dois pontos é inferior a um dado limiar consiste em calcular a distância e comparar com o limiar.
import math

def perigo(x1,y1,x2,y2,limiar):
    """ Determina se a distância entre dois pontos é inferior a um dado limiar."""
    dist = math.sqrt((x1 - x2)**2 + (y1 - y2)**2)
    if dist < limiar:
        return True
    else:
        return False
P3

Este programa tem duas partes. A primeira consiste em construir um programa que permita desenhar um rectângulo, controlando a posição, a orientação o comprimento dos lados e a cor. à semelhança do que foi feito nas aulas para o caso dos polígonos regulares, temos uma solução simples.
import turtle

def vela(posx, posy, orienta, lado1 ,lado2, cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orienta)
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado1)
        turtle.left(90)
        turtle.forward(lado2)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()
A segunda pergunta recorre ao primeiro programa e é trivial. Ter um ciclo em que vamos desenhando rectângulos desfasados de um dado ângulo.
def moinho(posx, posy,orienta, lado1, lado2, cor, n,afastamento):
    """ desenha as velas de um moinho"""
    angulo = orienta
    for i in range(n):
        vela(posx,posy, angulo, lado1,lado2,cor)
        angulo = angulo + afastamento
    turtle.hideturtle()
    turtle.exitonclick()

Teste 1 - TP1

Teste # 1 - TP1

P1

Em Python todos os programas que escrevemos quando são executados são-no num dado ambiente. Esse ambiente é composto pelo espaço de nomes, o lugar da memória onde se encontram os nomes activos, e o espaço dos objectos, o lugar da memória onde se encontram os objectos descritos através dos seus atributos.

Os dois espaços estão ligados entre si, no sentido de que os nomes estão associados a objectos. essa associação é feita através da instrução de atribuição (explicita ou implícita). Um exemplo simples:

Depois de fazermos a = 5 o nome a passa a existir no espaço de nomes, associado ao objecto de valor 5.

P2

Este problema é muito simples. Escrevo um programa que recebe como entrada o número de tentativas e o número em que digo que acerto. Depois tenho um ciclo que pode ser executado um número máximo de vezes igual ao número de tentativas e que termina com verdadeiro se sair o número, falso no caso contrário.
def aposta(n,m):
    for i in range(m):
        num = random.randint(1,6)
        if num == n:
            return True
    return False
P3

Este programa tem duas partes. Comecemos pela primeira: desenhar um losango colorido, numa dada posição e orientação e com um dado tamanho. Este problema não é muito diferente do que foi feito nas aulas envolvendo polígonos regulares. Daí a solução:
import turtle

def petala(posx, posy, orienta, lado, cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()
    turtle.setheading(orienta)
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(2):
        turtle.forward(lado)
        turtle.left(60)
        turtle.forward(lado)
        turtle.left(120)
    turtle.end_fill()
Começamos por levantar a caneta, posicionar a tartaruga, orientá-la e definir a cor. Depois é só executar as operações de avanço e rotação.

A segunda parte é mais fácil pois consiste em repetir o desenho dos losangos, mantendo a posição de referência e rodando o losango.
def flor(posx, posy,orienta, lado, cor, n,afastamento):
    """ desenha uma flor com n pétalas."""
    angulo = orienta
    for i in range(n):
        petala(posx,posy, angulo, lado,cor)
        angulo = angulo + afastamento
    turtle.hideturtle()
    turtle.exitonclick()