cyclope_snake.py

Created by fime

Created on April 30, 2021

9.68 KB

𝐂𝐲𝐜𝐥𝐨𝐩𝐞 𝐒𝐧𝐚𝐤𝐞

For Numworks N0110 and N0100 - Bug report or review here

Code by Fime - Also check out my 2 players Pong

Basic snake game with cool features.

Eat the maximum food you can, but dont touch the edges or an other part of the snake !

  • Arrows : move
  • If you wait a too long time, the snake gonna be crazy and will go in the opposite direction.
  • Best score saving won’t work on Epsilon. Install Omega here

𝐅𝐞𝐚𝐭𝐮𝐫𝐞𝐬

  • Good graphics
  • Gameplay changements
  • High personnalisation

𝐆𝐚𝐦𝐞 𝐩𝐫𝐞𝐬𝐨𝐧𝐧𝐚𝐥𝐢𝐬𝐚𝐭𝐢𝐨𝐧

  • For change the game speed : go line 32 and change the frames delay

  • For change the game size : go line 33 (auto resizement)

  • For change the angry time : go line 36

  • For change the colors : section #colors, line 38 :

    • DON’T CHANGE the background color (BG_COLOR), line 39
    • DON’T CHANGE the shadow color (SDW_COLOR), line 40
    • Edges color, line 41
    • Snake and food colors (juste add/modify/remove the colors of the list), line 42


"""
CyclopeSnake v0.5
by Fime
"""
from kandinsky import *
from random import randint
from ion import *
from time import *

LOGO=[
"011101001001100101101111",
"110001101010010110001100",
"001101011011110101101000",
"111001001010010100101111"]

INSTRUCTIONS="""Eat the maximum food you can,but
dont touch the edges or an other
part of the snake !
→ arrows : move
→ if you wait a too long time,
the snake gonna be crazy and
will go in the opposite
direction !
"""


#screen config
SCREEN_W=320
SCREEN_H=180

#game vars
START_SPEED=0.2
SIZE=14
HALF_SIZE=SIZE//2
DIRECTION={"L":[-1,0],"U":[0,-1],"R":[1,0],"D":[0,1]}
ANGRY_TIME=5

#colors
BG_COLOR=(255,255,255)
SDW_COLOR=(150,150,150)
EDGE_COLOR=(50,50,50)
COLORS=[[205,150,0],[100,175,0],[0,205,100],[0,150,205],[150,0,205],[205,0,50]]

#grid config
GRID_W=SCREEN_W//SIZE
GRID_H=SCREEN_H//SIZE
  
OFFSET_X=SCREEN_W%SIZE
OFFSET_Y=SCREEN_H%SIZE

LIMIT_X=[OFFSET_X,SCREEN_W-OFFSET_X]
LIMIT_Y=[OFFSET_Y,SCREEN_H-OFFSET_Y]

START_PO=[GRID_W//2*SIZE+HALF_SIZE+OFFSET_X//2,GRID_H//2*SIZE+HALF_SIZE+OFFSET_Y//2]

def game():
  """The game fonction.
  snake parts: list of the snake parts ([x],[y],[part color])
  snake length: actual limit size of the snake
  game score: score
  best score: best score. saved on snake.sav
  snake position: head's position
  snake direction: head's direction
  food info: food infos ([x],[y],[color])
  food timer: for the limit of the angry
  timer: game timer
  fps: frame par seconds
  """
  
  global s_part,s_len,score,best,po,vec,f_info,f_timer,timer,fps
  
  #draw the (n) element of snake
  def drawElement(n):
    
    #get element info
    inf=list(s_part[n])
    inf.append((inf[2][0]+50,inf[2][1]+50,inf[2][2]+50))
    
    #fill the main square and the detail
    fill_rect(inf[0]-HALF_SIZE,inf[1]-HALF_SIZE,SIZE,SIZE,inf[3])
    fill_rect(inf[0]-HALF_SIZE//2,inf[1]-HALF_SIZE//2,HALF_SIZE//2*2,HALF_SIZE//2*2,inf[2])
    
    drawShadow(inf)
  
  def drawEye():
    
    inf=list(s_part[-1])
    fill_rect(inf[0]-HALF_SIZE//2,inf[1]-HALF_SIZE//2,HALF_SIZE//2*2,HALF_SIZE//2*2,BG_COLOR)
    fill_rect(inf[0]-HALF_SIZE//3+vec[0],inf[1]-HALF_SIZE//3+vec[1],HALF_SIZE//3*2,HALF_SIZE//3*2,EDGE_COLOR)
  
  def drawShadow(inf):
    #draw shadow
    if get_pixel(inf[0],inf[1]+HALF_SIZE)==(248, 252, 248):#if nothing below, draw shadow
      fill_rect(inf[0]-HALF_SIZE,inf[1]+HALF_SIZE,SIZE,SIZE//2,SDW_COLOR)
  
  def hideElement(n):
    
    inf=list(s_part[n])#get part info
    fill_rect(inf[0]-HALF_SIZE,inf[1]-HALF_SIZE,SIZE,SIZE,BG_COLOR)#hide part
    hideShadow(inf)
    
  def hideShadow(inf):
    if get_pixel(inf[0]-HALF_SIZE,inf[1]+HALF_SIZE)==(144,148,144):#if shadow below
      fill_rect(inf[0]-HALF_SIZE,inf[1]+HALF_SIZE,SIZE,SIZE//2,BG_COLOR)
      
    if (get_pixel(inf[0],inf[1]-HALF_SIZE-1)!=(248,252,248)) and not(inf[1]-HALF_SIZE-1<OFFSET_Y):#if part above
      fill_rect(inf[0]-HALF_SIZE,inf[1]-HALF_SIZE,SIZE,HALF_SIZE,SDW_COLOR)
  
  def drawFood():
    #get element info
    inf=list(f_info)
    inf.append([inf[2][0]+50,inf[2][1]+50,inf[2][2]+50])
  
    #fill the main square and the detail
    fill_rect(inf[0]-HALF_SIZE,inf[1]-HALF_SIZE,SIZE,SIZE,inf[3])
    
    fill_rect(inf[0]-HALF_SIZE,inf[1]-HALF_SIZE,HALF_SIZE//2,SIZE,inf[2])
    fill_rect(inf[0],inf[1]-HALF_SIZE,HALF_SIZE//2,SIZE,inf[2])
    
    fill_rect(inf[0]-HALF_SIZE//2-1,inf[1]-HALF_SIZE,HALF_SIZE+1,HALF_SIZE//2+1,EDGE_COLOR)
    fill_rect(inf[0]-HALF_SIZE//2,inf[1]-HALF_SIZE,HALF_SIZE//2,HALF_SIZE//2,(0,200,100))
    fill_rect(inf[0],inf[1]-HALF_SIZE,HALF_SIZE//2,HALF_SIZE//2,(0,150,75))
    
    drawShadow(inf)
  
  def hideFood():
    
    inf=list(f_info)#get part info
    fill_rect(inf[0]-HALF_SIZE,inf[1]-HALF_SIZE,SIZE,SIZE,BG_COLOR)#hide part
    hideShadow(inf)
    
  
  def randPo():
    x=OFFSET_X//2+HALF_SIZE+randint(1,GRID_W-2)*SIZE
    y=OFFSET_Y//2+HALF_SIZE+randint(1,GRID_H-2)*SIZE
    return x,y
  
  def drawScore():
    fill_rect(0,SCREEN_H,320,20,EDGE_COLOR)
    draw_string("Time: {}s".format(int(monotonic()-timer)),0,SCREEN_H,BG_COLOR,EDGE_COLOR)
    draw_string("Score: {} ({})".format(score,best),110,SCREEN_H,BG_COLOR,EDGE_COLOR)
    
  def drawFoodTmr(val):
    lenght=int(val*300/ANGRY_TIME) if val<ANGRY_TIME else 300
    fill_rect(10,SCREEN_H+20,lenght,20,f_info[2])
    if val>=2.5:  
      draw_string("ANGRY",10,SCREEN_H+20,BG_COLOR,f_info[2])
    
  def death_annimation():
    
    for n in range(s_len):
      #hide the elements from the last to the fist
      hideElement(n)
      #s_part.remove(s_part[n])
      sleep(1/s_len)
    
    for y in range(0,222,10):
      fill_rect(0,y,SCREEN_W,10,EDGE_COLOR)
      sleep(0.05)
      
    dead_msg="GAME OVER"
    for y in range(-10,222//2-10,10):
      draw_string(dead_msg,SCREEN_W//2-5*len(dead_msg),y,BG_COLOR,EDGE_COLOR)
      sleep(0.05)
      fill_rect(SCREEN_W//2-5*len(dead_msg),y,10*len(dead_msg),20,EDGE_COLOR)
    draw_string(dead_msg,SCREEN_W//2-5*len(dead_msg),y,BG_COLOR,EDGE_COLOR)
    x,y=SCREEN_H//2,SCREEN_H//2-40

  #init vars
  s_part,s_len,s_color,hue,f_hue,po=[],5,COLORS[0],0,0,list(START_PO)
  f_info, f_timer,angry=[randPo()[0],randPo()[1],COLORS[0]],float(monotonic()),0
  score,key,frame,frame_count,timer,speed=0,"L",1,0,float(monotonic()),START_SPEED
  best=int(readBestScore())
  
  print(str(po))
  #init screen  
  fill_rect(0,0,320,222,EDGE_COLOR)
  fill_rect(OFFSET_X//2,OFFSET_Y//2,SCREEN_W-OFFSET_X,SCREEN_H-OFFSET_Y,(BG_COLOR))
  
  drawFood()
  print("Starting game...")
      
  while True:
    
    frame_count+=1
    
    #frame for move
    if monotonic()-frame>=speed:
      
      #fps
      fps=int(frame_count/(monotonic()-timer))
      
      #reset second count
      frame=monotonic()
      
      vec=list(DIRECTION[key])#get direction
      
      po[0]+=vec[0]*SIZE#change position
      po[1]+=vec[1]*SIZE
      
      #score
      drawScore()
      
      #dead hitbox
      dead=0
      if LIMIT_X[0]>po[0] or po[0]>LIMIT_X[1]:#out of area
        dead=1
      elif LIMIT_Y[0]>po[1] or po[1]>LIMIT_Y[1]:
        dead=1
      if po[0:2] in [s_part[n][0:2] for n in range(len(s_part))]:
          dead=1
          print("touch")
      
      if dead:
        print("Game over...\nFrame count : {}\nGame during : {}\nFps : {}\nScore : {}\nSpeed : {}".format(frame_count,monotonic()-timer,fps,score,speed))
        death_annimation()#game over
        saveScore(best)
        break
      
      else:#normal move
        
        #actualize position
        s_part.append([po[0],po[1],s_color])
        change_color=0
        
        #food hitbox
        if po[0:2]==f_info[0:2]:
          hideFood()
          s_len+=1
          score+=1000
          speed=speed/1.02
          
          f_timer=monotonic()
          fill_rect(0,SCREEN_H+20,320,20,EDGE_COLOR)
          change_color=1
          loop=0
          
          f_hue=0 if f_hue==len(COLORS)-1 else f_hue+1
          f_info[2]=COLORS[f_hue]
          
          while True:
            f_info[0:2]=randPo()[0],randPo()[1]
            loop+=1
            if not f_info[0:2] in [i[0:2] for i in s_part]:
              break
            if loop>GRID_W*GRID_H:
              break
          
          hue=f_hue
          for n in range(len(s_part)):
            s_part[n][2]=COLORS[hue]
            drawElement(n)
          drawFood()
          
        score+=20
        if score>best:
          best=score
        
        if monotonic()-f_timer>=ANGRY_TIME:
          angry=1
          change_color=1
          hue=0 if hue==len(COLORS)-1 else hue+1
          draw_string("CRAZY !",10,SCREEN_H+20,BG_COLOR, f_info[2])
        
        else:
          angry=0
          drawFoodTmr(monotonic()-f_timer)
          
        s_color=COLORS[hue]
        if len(s_part)>=2:
          drawElement(-2)
        drawElement(-1)
        drawEye()
    
    if len(s_part)>s_len:#len limit
      hideElement(0)
      s_part.remove(s_part[0])
    
    #controls
    if keydown(KEY_LEFT) and vec[0]==0:
      key="R" if angry else "L"
    elif keydown(KEY_UP) and vec[1]==0:
      key="D" if angry else "U"
    elif keydown(KEY_RIGHT) and vec[0]==0:
      key="L" if angry else "R"
    elif keydown(KEY_DOWN) and vec[1]==0:
      key="U" if angry else "D"
  
  ######game over
  text="\t\tScore :{}\n\t\tBest Score :{}".format(score,best)
  x=320//2-(len(LOGO[0])*10)//2
  drawLogo(x,10,10,BG_COLOR)
  draw_string(text,0,150,BG_COLOR,EDGE_COLOR)
  while True:
    if keydown(KEY_OK):
      while keydown(KEY_OK):
        pass
      break
    if int(monotonic())%2==1:
      draw_string("press <OK>",220,200,(255,100,100),EDGE_COLOR)
    else:
      fill_rect(220,200,140,20,EDGE_COLOR)
  game()

  

def main_menu():
  """the main menu"""
  fill_rect(0,0,320,222,EDGE_COLOR)
  x=320//2-(len(LOGO[0])*10)//2
  drawLogo(x,5,10,BG_COLOR)
  draw_string(INSTRUCTIONS,0,60,(150,150,150),EDGE_COLOR)
  while True:
    if keydown(KEY_OK):
      while keydown(KEY_OK):
        pass
      break
    if int(monotonic())%2==1:
      draw_string("press <OK>",220,200,(255,50,50),EDGE_COLOR)
    else:
      fill_rect(220,200,140,20,EDGE_COLOR)
  game()
  
  
def drawLogo(x,y,size,color):
  """draw the logo at [x,y]
  with the specified size for each pixel
  and the specified color
  """
  for yOf in range(len(LOGO)):
    for xOf in range(len(LOGO[yOf])):
      if LOGO[yOf][xOf]=="1":
        fill_rect(x+xOf*size,y+yOf*size,size,size,color)

def readBestScore():
  """
  read and return the best score
  saved in snake.sav
  return 0 if reading failed
  """
  try:
    file=open("snake.sav","r")
    best=file.readline()
    file.close()
    return int(best)
  except:
    return 0

def saveScore(score):
  """
  erase the actual best score in snake.sav
  then write [score] on it.
  """
  try:
    file=open("snake.sav","w")
    file.truncate(0)
    file.write(str(score))
    file.close()
  except:
    pass

main_menu()