synchrod.py

Created by andreanx

Created on September 12, 2021

10.7 KB


from kandinsky import set_pixel as poly_set_pixel, fill_rect
from ion import keydown as poly_test_key, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_EXE as KEY_ENTER, KEY_BACK as KEY_ESC

screen_w, screen_h = 320, 222

def wait_key():
    while 1:
      for k in (KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_ENTER, KEY_ESC):
          if poly_test_key(k):
              return k

SCALE = min(screen_w // 128, screen_h // 64)

YELLOW, RED, BLUE, GREEN, ALL = 0, 1, 2, 3, 4
VOID, WALL, SPIKES, MONSTER, TRAP, EXIT = 100, 101, 102, 107, 112, 117
GO_UP, GO_RIGHT, GO_DOWN, GO_LEFT, ATTACK, QUIT = 10, 11, 12, 13, 14, 15
NEW_GAME, TRAP_APPEARED, SPIKES_APPEARED = 20, 21, 22

JAUNE, ROUGE, BLEU, VERT, TOUS = 0, 1, 2, 3, 4
VIDE, MUR, PICS, MONSTRE, PIEGE, SORTIE = 100, 101, 102, 107, 112, 117
ALLER_HAUT, ALLER_DROITE, ALLER_BAS, ALLER_GAUCHE, ATTAQUER, QUITTER = 10, 11, 12, 13, 14, 15
NOUVELLE_PARTIE, PIEGE_APPARU, PICS_APPARUS = 20, 21, 22

def ri(rs,a,b):
  rs[0] = (rs[0] * 214013 + 2531011) % 4294967296
  r = (rs[0] // 65536) & 0x7fff
  return r % (b-a) + a

### Rendering

def px(x, y, c):
    for sx in range(SCALE):
        for sy in range(SCALE):
            poly_set_pixel(x*SCALE+sx, y*SCALE+sy, c)

colors = [(77,71,63), (210,217,217), (241,233,103), (234,96,88), (97,156,236), (111,226,126), (178,126,206)]
s=0x002a2a55005454aa00
m=0x00005ae7bd18000000
t=0x00182c4a5234180000
e=0xf080e0c080e0c0f000
f=0x0f0107030107030f00
p=0x386565396dd7ff7d00
tiles = [0,0xff818181818181ff01,s+2,s+3,s+4,s+5,s+6,m+2,m+3,m+4,m+5,m+6,t+2,t+3,t+4,t+5,t+6,f+2,e+3,f+4,e+5]
sprites = [p+2,p+3,p+4,p+5]

def render_image(tile, x0, y0):
    fg, bg = colors[tile & 0xff], colors[0]
    tile >>= 8
    if tile:
        for y in range(8):
            for x in range(8):
                px(x0*8+7-x, y0*8+7-y, fg if tile & 1 else bg)
                tile >>= 1
    else:
        fill_rect(x0*8*SCALE, y0*8*SCALE, 8*SCALE, 8*SCALE, bg)


def render_game(board, players, old_players, update):
    for xy in update:
        if xy == -1:
            for y in range(8):
                for x in range(16):
                    render_image(tiles[board[16*y+x]-VOID], x, y)
        else:
            render_image(tiles[board[xy]-VOID], xy%16, xy//16)
    for xy in old_players:
        render_image(tiles[board[xy]-VOID], xy%16, xy//16)
    for i in range(4):
        render_image(sprites[i], players[i]%16, players[i]//16)

### API for submissions

def input_action():
    interactive_actions = {KEY_UP:GO_UP, KEY_RIGHT:GO_RIGHT, KEY_DOWN:GO_DOWN, KEY_LEFT:GO_LEFT, KEY_ENTER:ATTACK, KEY_ESC:QUIT}
    i = -1
    while not i in interactive_actions.keys():
        i = wait_key()
    return interactive_actions.get(i)

def is_a(obj, kind):
    if kind == VOID or kind == WALL:
        return obj == kind
    return kind <= obj <= kind + 4

def affects(obj, player):
    return (is_a(obj, SPIKES) or is_a(obj, MONSTER) or is_a(obj, TRAP)) and obj != SPIKES+player and obj != MONSTER+player and obj != TRAP+player

pathfind_d = [-16, +16, -1, +1, GO_UP, GO_DOWN, GO_LEFT, GO_RIGHT]

def pathfind(board, start, end):
    n = len(board)
    parent = [-1]*n
    directions = [-1]*n
    queue = [start]
    parent[start] = start
    cell = -1

    while queue:
        cell = queue.pop(0)
        if cell == end:
            break
        for i in range(4):
            d = cell+pathfind_d[i]
            if board[d] != WALL and parent[d] < 0:
                parent[d] = cell
                directions[d] = pathfind_d[i+4]
                queue.append(d)

    if cell == end:
        path = []
        while cell != start:
            path = [directions[cell]] + path
            cell = parent[cell]
        return path

demander_action, est_un, affecte, calculer_chemin = input_action, is_a, affects, pathfind

### Board generator

def ufc():
    return [i for i in range(128)]
def ufr(uf,i):
    if uf[i] == i:
        return i
    rep = ufr(uf, uf[i])
    uf[i] = rep
    return rep
def ufm(uf,i,j):
    uf[ufr(uf,i)]=ufr(uf,j)

def gb(rs):
    board = [WALL] * 128
    pp = [17,30,97,110]
    pe = [47,80,95,32]
    for i in range(4):
        board[pp[i]] = VOID
        board[pe[i]] = EXIT+i
    uf = [i for i in range(128)]
    while any(ufr(uf,pp[i]) != ufr(uf,pe[i]) for i in range(4)):
        i = ri(rs,1,7)*16 + ri(rs,1,15)
        if board[i] == WALL and any(board[n] != VOID for n in (i-16,i+16,i-1,i+1)):
            board[i] = VOID
            for n in (i-16,i+16,i-1,i+1):
                if board[n] != WALL:
                    ufm(uf,i,n)
    for i in range(128):
        if board[i] == VOID and ri(rs,0,16)<2:
            board[i]=[MONSTER, MONSTER, SPIKES, TRAP][ri(rs,0,4)]+ri(rs,0,5)
    return board

### Game logic

def play_board(rs, turn_function, blind):
    board = gb(rs)
    players = [17,30,97,110]
    old_players = []
    ev = [(-1,-1,NEW_GAME,-1)]
    update = [-1]
    turns = 0
    penalty = 0

    while True:
        if not blind:
            render_game(board, players, old_players, update)
        action = turn_function(board, players, ev)
        ev = []
        update = []
        old_players = players[:]
        if action is None:
            continue
        if action == QUIT:
            return None

        turns += 1
        traps_activated = 0

        for p in range(4):
            coord = players[p]
            if coord < 0:
                continue

            if action == ATTACK:
                for direction in range(4):
                    dx = (direction == 1) - (direction == 3)
                    dy = (direction == 2) - (direction == 0)
                    i = coord + dx + 16*dy
                    dest = board[i]

                    if is_a(dest, MONSTER):
                        board[i] = VOID
                        update.append(i)
                    elif is_a(dest, TRAP) and affects(dest, p):
                        traps_activated += 1
                        board[i] = VOID
                        update.append(i)
            else:
                dx = (action == GO_RIGHT) - (action == GO_LEFT)
                dy = (action == GO_DOWN) - (action == GO_UP)
                i = coord + dx + 16*dy
                dest = board[i]

                if dest == EXIT+p:
                    players[p] = -1
                    continue
                if dest == WALL or is_a(dest, EXIT):
                    continue

                players[p] = i
                if not affects(dest, p):
                    continue

                if is_a(dest, SPIKES):
                    penalty += 10
                elif is_a(dest, MONSTER):
                    penalty += 10
                    board[i] = VOID
                    update.append(i)
                elif is_a(dest, TRAP):
                    traps_activated += 1
                    board[i] = VOID
                    update.append(i)

        while traps_activated > 0:
            effect = ri(rs,0,3)
            if effect == 0:
                penalty += 10
            x = ri(rs,1,15)
            y = ri(rs,1,7)
            if board[y*16+x] == VOID and y*16+x not in players:
                board[y*16+x] = (TRAP if effect==1 else SPIKES) + ri(rs,0,4)
                ev.append((x, y, TRAP_APPEARED if effect==1 else SPIKES_APPEARED, p))
                update.append(y*16+x)
            traps_activated -= 1

        if all(p < 0 for p in players):
            score = 150 - penalty - turns
            print("Bravo! "+str(turns)+"T "+str(penalty)+"D -> "+str(score))
            return score

def play_game(turn_function, blind=False, seed=0xc0ffee, maxgames=100):
    rs = [seed]
    games = 0
    score = 0
    while games != maxgames:
        print("#"+str(games)+": "+str(rs[0]))
        board_score = play_board(rs, turn_function, blind)
        if board_score is None:
            print("")
            print("Quit!")
            return
        else:
            score += max(board_score, 0)
            games += 1
    print("Games solved:", games)
    print("Score:", score)

def tour_ask(plateau, joueurs, evenements):
    # Demander une action avec input()
    return demander_action()

def tour_path(plateau, joueurs, evenements):
    global joueur_courant, chemin

    # Réinitialisation en début de partie
    for (x, y, ev, joueur) in evenements:
        if ev == NOUVELLE_PARTIE:
            joueur_courant = 0
            chemin = []

    # Si le joueur est arrivé à sa destination, on passe au suivant.
    # Il arrive que plusieurs joueurs sortent en un seul tour, si ça se produit
    # on continue de passer au joueur suivant (il en reste forcément un qui
    # n'est pas sorti sinon la partie serait finie).
    while joueurs[joueur_courant] == -1:
        joueur_courant += 1
        chemin = []

    # Chemin du joueur actuel vers sa sortie
    if chemin == []:
        case_sortie = plateau.index(SORTIE + joueur_courant)
        chemin = calculer_chemin(plateau, joueurs[joueur_courant], case_sortie)

    # Prochaine étape
    mouvement = chemin[0]
    chemin = chemin[1:]

    return mouvement

# Ordre des joueurs à sortir
ordre_de_sortie = [0, 2, 1, 3]
# Position du joueur qu'on veut sortir dans ordre_de_sortie
joueur_courant_id = 0
# Chemin pour le sortir
chemin = []

def tour_greedy(plateau, joueurs, evenements):
    global joueur_courant_id, chemin

    for (x, y, ev, joueur) in evenements:
        if ev == NOUVELLE_PARTIE:
            joueur_courant_id = 0
            chemin = []

    # Si le joueur est arrivé à sa destination, on passe au suivant
    while joueurs[ordre_de_sortie[joueur_courant_id]] == -1:
        joueur_courant_id += 1
        chemin = []

    joueur_courant = ordre_de_sortie[joueur_courant_id]

    # Chemin du joueur actuel vers sa sortie
    if chemin == []:
        case_sortie = plateau.index(SORTIE + joueur_courant)
        chemin = calculer_chemin(plateau, joueurs[joueur_courant], case_sortie)

    # S'il y a des monstres autour mais pas de piège, attaquer
    monstres_autour = False
    pieges_autour = False

    for joueur in joueurs:
        # On ne compte pas les joueurs qui ont déjà sortis
        if joueur != -1:
            if est_un(plateau[joueur-1], MONSTRE) or \
               est_un(plateau[joueur+1], MONSTRE) or \
               est_un(plateau[joueur-16], MONSTRE) or \
               est_un(plateau[joueur+16], MONSTRE):
                monstres_autour = True
            if est_un(plateau[joueur-1], PIEGE) or \
               est_un(plateau[joueur+1], PIEGE) or \
               est_un(plateau[joueur-16], PIEGE) or \
               est_un(plateau[joueur+16], PIEGE):
                pieges_autour = True

#    if monstres_autour and not pieges_autour:
#        return ATTAQUER

    # Prochaine étape
    mouvement = chemin[0]
    chemin = chemin[1:]

    return mouvement

print("play_game(tour_ask, blind=False)")
print("play_game(tour_path, blind=True)")
print("play_game(tour_greedy, blind=True)")

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our cookies policy.