pianota_dev.py

Created by sandraev

Created on February 03, 2023

13.9 KB


from kandinsky import *
from random import randint
from ion import *
from time import sleep
from os import listdir


#####################################
# At first we define some constants #
#####################################

PROG_VER = "1.9.1"

SCREEN_W = 320
SCREEN_H = 222

KEYS = [KEY_COSINE,KEY_TANGENT,KEY_PI,KEY_SQRT]
COLORS = [([22,102,222],[80,160,255]),
([140,80,180],[215,155,255]),
([180,50,50],[255,125,125]),
([180,60,30],[255,135,100]),
([180,140,0],[255,215,75]),
([0,180,40],[75,255,115]),
([180,180,180],[240,240,240])]

BG_COLOR = [0,0,0]

NOTE_SPEED = 2
NOTE_SPACE = 6
NOTE_H = 46

POLY_MODE = 0
POLY_ACCURACY = 9

PERFECT_MODE = 1
PERFECT_NB = 5

INFINITE_MODE = 0

LVL_LEN = 100


#########################
# We define our classes #
#########################

class Vector():
    """Describes the structure of a vector"""
    def __init__(self,x=0,y=0):
        self.x,self.y = x,y

class ColorScheme():
    """Defines a simple three-color scheme for tiles"""
    def __init__(self,primary_color,secondary_color,background_color):
        self.primary_color = primary_color
        self.secondary_color = secondary_color
        self.background_color = background_color

class Tile():
    """Defines a single tile"""
    def __init__(self, x:int, start_y:int, end_y:int, size:Vector, speed:int, colors:ColorScheme, order:int):
        self.x = x
        self.start_y,self.end_y = start_y,end_y
        self.size = size
        self.y = self.start_y - self.size.y
        self.speed = speed
        self.colors = colors
        self.order = order
        self.is_active = False
    def flash(self):
        color = self.colors.primary_color
        hidden_part = self.y + self.size.y - self.end_y
        if self.is_active:
            color = self.colors.secondary_color
        if hidden_part < 0:
            hidden_part = 0
        if hidden_part < self.size.y:
            fill_rect(self.x, self.y, self.size.x, self.size.y - hidden_part, color)
    def update(self):
        self.y += self.speed

        color = self.colors.primary_color
        hidden_part = self.y + self.size.y - self.end_y
        if self.is_active:
            color = self.colors.secondary_color
        if hidden_part < 0:
            hidden_part = 0
        if hidden_part <= self.size.y:
            fill_rect(self.x, self.y - self.speed, self.size.x, self.speed, self.colors.background_color)
            if hidden_part <= self.size.y - self.speed:
                fill_rect(self.x, self.y + self.size.y - self.speed - hidden_part, self.size.x, self.speed, color)

class Button():
    """Aiming at replace the one define in NWTK"""
    def __init__(self,x,y,txt,txt_color,btn_color,bg_color):
        self.focused = False
        self.x,self.y = x,y
        self.txt = txt
        self.txt_color = txt_color
        self.btn_color = btn_color
        self.bg_color = bg_color
    def draw(self):
        real_btn_color = self.btn_color
        real_txt_color = self.txt_color
        if self.focused:
            real_btn_color = self.txt_color
            real_txt_color = self.btn_color
        fill_rect(self.x,self.y,len(self.txt)*7 + 4,14 + 4,real_btn_color)
        draw_string(self.txt,self.x + 2,self.y + 2,real_txt_color,real_btn_color,1)
        set_pixel(self.x,self.y,self.bg_color)
        set_pixel(self.x + len(self.txt)*7 + 4 - 1,self.y,self.bg_color)
        set_pixel(self.x + len(self.txt)*7 + 4 - 1,self.y + 14 + 4 - 1,self.bg_color)
        set_pixel(self.x,self.y + 14 + 4 - 1,self.bg_color)

class NoteGrid():
    """Defines a grid and its geometry for tiles"""
    def __init__(self,w,total_column_nb,padding):
        self.total_column_nb = total_column_nb
        self.padding = padding
        self.w = w
        self.x = int(SCREEN_W/2-self.w/2)
        self.col_w = int(self.w/self.total_column_nb)
        self.draw()
    def draw(self):
        draw_line(self.x,0,self.x,SCREEN_H,[60,60,60])
        draw_line(int(self.x+self.col_w*1),0,int(self.x+self.col_w*1),SCREEN_H,[60,60,60])
        draw_line(int(self.x+self.col_w*2),0,int(self.x+self.col_w*2),SCREEN_H,[60,60,60])
        draw_line(int(self.x+self.col_w*3),0,int(self.x+self.col_w*3),SCREEN_H,[60,60,60])
        draw_line(int(self.x+self.col_w*4),0,int(self.x+self.col_w*4),SCREEN_H,[60,60,60])

class Note():
    """Defines a single tile"""
    def __init__(self,Grid,column_nb,y,h,color,active_color,bg_color):
        self.Grid = Grid
        self.column_nb = column_nb
        self.x = Grid.x + Grid.col_w * column_nb + Grid.padding
        self.w = Grid.col_w - Grid.padding * 2
        self.y,self.h = y,h
        self.color,self.bg_color = color,bg_color
        self.active_color = active_color
        self.speed = 1
        self.active = False
    def step(self):
        if self.y < 200:
            if self.active:
                fill_rect(self.x,self.y,self.w,self.speed,self.active_color)
            else:
                fill_rect(self.x,self.y,self.w,self.speed,self.color)
        fill_rect(self.x,self.y-self.h,self.w,self.speed,self.bg_color)
    def flash(self):
        if not self.active:
            fill_rect(self.x,self.y-self.h,self.w,self.h,self.color)
        else:
            fill_rect(self.x,self.y-self.h,self.w,self.h,self.active_color)
        if self.y > 200:
            fill_rect(self.x,200,self.w,self.y-200,self.bg_color)

class TouchLine():
    """Defines the deadline for the tiles"""
    def __init__(self,y,h,color):
        self.y,self.h = y,h
        self.color = color
    def draw(self):
        fill_rect(0,self.y,320,self.h,self.color)

class ColTxt():
    """Defines a single text align within a column"""
    def __init__(self,y,Grid,column_nb,txt,color,active_color,bg_color):
        self.y = y
        self.Grid = Grid
        self.column_nb = column_nb
        self.txt = txt
        self.color = color
        self.active_color = active_color
        self.bg_color = bg_color
        self.active = False
    def draw(self,color=None):
        if color == None:
            color = self.active_color
        if self.active:
            draw_string(self.txt,int(self.Grid.x+self.Grid.col_w*self.column_nb)+int(self.Grid.col_w/2-len(self.txt)*7/2),self.y,color,self.bg_color,1)
        else:
            draw_string(self.txt,int(self.Grid.x+self.Grid.col_w*self.column_nb)+int(self.Grid.col_w/2-len(self.txt)*7/2),self.y,self.color,self.bg_color,1)


#######################################
# Now we define some useful functions #
#######################################

def transtion():
  for i in range(222):
    draw_line(0,i,80,i,[10,10,40])
    sleep(0.0005)
  for i in range(222):
    draw_line(240,i,320,i,[10,10,40])
    sleep(0.0005)
  for i in range(222):
    draw_line(80,i,160,i,[10,10,40])
    sleep(0.0005)
  for i in range(222):
    draw_line(160,i,240,i,[10,10,40])
    sleep(0.0005)
  sleep(0.2)

def load_score():
  if "nota.score" in listdir():
    file = open("nota.score","r")
    score = file.readline()
    file.close()
  else:
    file = open("nota.score","w+")
    score = 0
    file.close()
  if score == "":
    score = 0
  return score

def save_score(score):
  file = open("nota.score","w")
  file.truncate(0)
  file.write(str(score))
  file.close()

def main_menu():
  fill_rect(0,0,320,222,"black")

  notes = []
  color_nb = 0
  pixel = 0

  note_grid = NoteGrid(180,4,4)
  touch_line = TouchLine(200,2,[212,42,26])

  txt = "PiaNOTA"
  for i in range(len(txt)):
    draw_string(txt[i],int(125+i*10),50,[0,0,0],COLORS[i][1])
  draw_string("A PianoTiles remix!",92,70,[200,200,200],[0,0,0],1)

  keys = [ColTxt(160,note_grid,0,"[cos]",[100,100,100],[240,240,240],[0,0,0]),
  ColTxt(160,note_grid,1,"[tan]",[100,100,100],[240,240,240],[0,0,0]),
  ColTxt(160,note_grid,2,"[pi]",[100,100,100],[240,240,240],[0,0,0]),
  ColTxt(160,note_grid,3,"[sqrt]",[100,100,100],[240,240,240],[0,0,0])]

  keys_colors = []

  for i in keys:
    i.draw()
    keys_colors.append(COLORS[randint(0,6)][1])

  play_bt = Button(134,120,"PLAY !",[255,255,255],[32,112,232],[0,0,0])
  play_bt.draw()

  draw_string("Best score: ",10,198,[220,60,70],[0,0,0],1)
  draw_string(str(load_score()),90,195,[255,80,90],[0,0,0])

  draw_string("V "+str(PROG_VER),320-12-int((2+len(str(PROG_VER)))*7),198,[255,140,70],BG_COLOR,1)

  while True:
    for i in KEYS:
      if keydown(i):
        keys[KEYS.index(i)].active = True
        keys_colors[KEYS.index(i)] = COLORS[randint(0,6)][1]
      else:
        keys[KEYS.index(i)].active = False
      keys[KEYS.index(i)].draw(keys_colors[KEYS.index(i)])

    if keydown(KEY_OK):
      play_bt.focused = True
      play_bt.draw()
      while keydown(KEY_OK): pass
      play()
      break

def down_message(txt_color,color,txt):
  for i in range(24):
    draw_line(0,200-i,320,200-i,color)
    sleep(0.01)
  draw_string(txt,int(SCREEN_W/2-len(txt)*10/2),180,txt_color,color)
  sleep(2.5)

def game_over(msg=""):
  down_message([220,220,220],[212,42,26],"Game Over")
  print(msg)
  transtion()


################################
# And here is the main program #
################################

def play(starting_score=0):
  #TODO: Remove these (awful!) global variables
  global INFINITE_MODE,POLY_MODE

  transtion()
  fill_rect(0,0,320,222,[0,0,0])

  note_grid = NoteGrid(180,4,4)
  touch_line = TouchLine(200,2,[212,42,26])

  notes = []
  notes_active_screen_nb = 0

  color_nb = 0
  waiting = 0
  score = starting_score
  perfect = 0
  lvl_nb = 1
  best_score = int(load_score())
  pixel = 0
  sleep_time = 0.007
  col_active = {0:0,1:0,2:0,3:0}

  remains = {}
  for i in KEYS:
    remains[i] = [False,0]

  draw_string("Score :",0,0,[220,220,220],[0,0,0],1)
  draw_string(str(score),0,14,[200,120,80],[0,0,0])

  draw_string("Level :",320-49,0,[220,220,220],[0,0,0],1)
  if INFINITE_MODE:
    draw_string("...",320-30-14,14,[0,0,0],COLORS[6][1])
  else:
    draw_string(str(lvl_nb),320-30,14,[0,0,0],COLORS[color_nb][0])

  if NOTE_SPEED % 2 != 0:
    note_speed_margin = NOTE_SPEED-1
  else:
    note_speed_margin = NOTE_SPEED

  while True:
    # Note adding
    if not waiting:
      if pixel % (NOTE_H+NOTE_SPACE+note_speed_margin) == 0:
        last_note_col = randint(0,3)

        if INFINITE_MODE:
          color_nb = randint(0,len(COLORS)-1)

        notes.append(Note(note_grid,last_note_col,-(NOTE_H+NOTE_SPACE),NOTE_H,COLORS[color_nb][0],COLORS[color_nb][1],[0,0,0]))

        # Poly-Mode feature !
        if POLY_MODE:
          if randint(0,6)+randint(0,6) == POLY_ACCURACY:
            note_col = randint(0,3)
            while note_col - last_note_col < 2 and note_col - last_note_col > -2:
              note_col = randint(0,3)
            notes.append(Note(note_grid,note_col,-(NOTE_H+NOTE_SPACE),NOTE_H,COLORS[color_nb][0],COLORS[color_nb][1],[0,0,0]))

    # Key listener
    for i in KEYS:
      if keydown(i) and not remains[i][0]:
        col_active[KEYS.index(i)] = 1
        remains[i] = [True,pixel]
      if remains[i][0]:
        if remains[i][1]+int(NOTE_SPACE*NOTE_SPEED*2) < pixel:
          remains[i][0] = False

    # Pause feature
    if keydown(KEY_OK):
      while keydown(KEY_OK): pass
      draw_string("Game Paused",int(320/2-11*7/2),int(222/2-14/2),[0,0,0],[200,200,200],1)
      while not keydown(KEY_OK): pass
      while keydown(KEY_OK): pass
      draw_string("Game Paused",int(320/2-11*7/2),int(222/2-14/2),[0,0,0],[0,0,0],1)
      for i in notes:
        i.flash()
      touch_line.draw()

    # Note step
    for i in notes:
      i.step()
      i.speed = NOTE_SPEED
      i.y += NOTE_SPEED

      # Note activation
      if not i.active:
        if i.y > 5 and i.y-i.h < touch_line.y: # Note is visible
          if col_active[i.column_nb]:
            i.active = True
            notes_active_screen_nb += 1
            col_active[i.column_nb] = False
            i.flash()

            # Check if this is the last note
            if notes.index(i) > notes_active_screen_nb -1 and not POLY_MODE:
              if score > best_score:
                save_score(score)
              game_over("GO: Note activated was not the last one !")
              print("DBG: Note index was "+str(notes.index(i)))
              return

            # Update score
            score += 1
            if score > best_score:
              draw_string(str(score),0,14,[0,0,0],[220,120,80])
            else:
              draw_string(str(score),0,14,[220,120,80],[0,0,0])

            # Pixel perfect feature
            if PERFECT_MODE:
              if i.y+NOTE_SPACE > touch_line.y and i.y-i.h < touch_line.y:
                perfect += 1
              elif perfect != 0:
                perfect = 0
                draw_string("Perfect!",2,120,BG_COLOR,BG_COLOR,1)

              if perfect > PERFECT_NB:
                score += 1
                if perfect == PERFECT_NB+1:
                  draw_string("Perfect!",2,120,BG_COLOR,COLORS[color_nb][1],1)

      # Note escape
      if i.y > 280:
        if not i.active:
          if score > best_score:
            save_score(score)
          game_over("GO: Note still active !")
          return
        notes_active_screen_nb -= 1
        notes.remove(i)

    # Check for active columns
    for i in col_active:
      if col_active[i]:
        if score > best_score:
          save_score(score)
        game_over("GO: Column activated without reason !")
        return

    # Level change
    if (score/lvl_nb > LVL_LEN) and score != 0:
      if not INFINITE_MODE:
        waiting = 1
    if waiting:
      if len(notes) == 0:
        color_nb += 1
        lvl_nb += 1
        sleep_time -= 0.001
        waiting = 0

        perfect = 0
        draw_string("Perfect!",2,120,BG_COLOR,BG_COLOR,1)

        if lvl_nb > 7:
          down_message([0,0,0],[255,140,70],"INFINITE MODE Enabled !")
          save_score(score)
          transtion()

          sleep(0.5)

          INFINITE_MODE = 1
          POLY_MODE = 1
          play(score)
          return
        else:
          draw_string(str(lvl_nb),320-30,14,BG_COLOR,COLORS[color_nb][0])

    pixel += NOTE_SPEED
    touch_line.draw()
    if not INFINITE_MODE:
      sleep(sleep_time)


# The game loop...
while True:
  main_menu()

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.