11/* 
22@title : SimpleSnake
3- @description : Just a simple snake game, with kinda good art(?) and some sounds for it.
4- @author : @wolf-yuan-6115
3+ @author : wolf-yuan-6115
54@tags : []
65@addedOn : 2025-08-14
76*/ 
@@ -27,6 +26,7 @@ const single_right = "y"
2726const  food  =  "f" 
2827const  wall  =  "z" 
2928const  empty  =  "e" 
29+ const  dark_bg  =  "L" 
3030
3131setLegend ( 
3232  // Snake head sprites 
@@ -396,7 +396,24 @@ setLegend(
396396................ 
397397................ 
398398................ 
399- ................` ] 
399+ ................` ] , 
400+   [ dark_bg ,  bitmap ` 
401+ LLLLLLLLLLLLLLLL 
402+ LLLLLLLLLLLLLLLL 
403+ LLLLLLLLLLLLLLLL 
404+ LLLLLLLLLLLLLLLL 
405+ LLLLLLLLLLLLLLLL 
406+ LLLLLLLLLLLLLLLL 
407+ LLLLLLLLLLLLLLLL 
408+ LLLLLLLLLLLLLLLL 
409+ LLLLLLLLLLLLLLLL 
410+ LLLLLLLLLLLLLLLL 
411+ LLLLLLLLLLLLLLLL 
412+ LLLLLLLLLLLLLLLL 
413+ LLLLLLLLLLLLLLLL 
414+ LLLLLLLLLLLLLLLL 
415+ LLLLLLLLLLLLLLLL 
416+ LLLLLLLLLLLLLLLL` ] 
400417) 
401418
402419setSolids ( [ head_up ,  head_down ,  head_left ,  head_right ,  single_up ,  single_down ,  single_left ,  single_right ,  wall ] ) 
@@ -436,7 +453,30 @@ const startMelody = tune`
436453const  GAME_STATE  =  { 
437454  START_SCREEN : 0 , 
438455  PLAYING : 1 , 
439-   GAME_OVER : 2 
456+   GAME_OVER : 2 , 
457+   SETTINGS : 3 
458+ } 
459+ 
460+ // Settings system 
461+ const  THEMES  =  { 
462+   LIGHT : 0 , 
463+   DARK : 1 
464+ } 
465+ 
466+ const  SPEEDS  =  { 
467+   LOW : 0 , 
468+   MEDIUM : 1 , 
469+   HIGH : 2 
470+ } 
471+ 
472+ let  gameSettings  =  { 
473+   theme : THEMES . LIGHT , 
474+   speed : SPEEDS . MEDIUM 
475+ } 
476+ 
477+ let  settingsMenu  =  { 
478+   selectedOption : 0 , 
479+   options : [ 'Theme' ,  'Speed' ] 
440480} 
441481
442482let  gameState  =  GAME_STATE . START_SCREEN 
@@ -468,7 +508,19 @@ const startScreenMap = map`
468508.......... 
469509..........` 
470510
471- let  snake  =  [ {  x : 4 ,  y : 5  } ] 
511+ const  settingsScreenMap  =  map ` 
512+ .......... 
513+ .......... 
514+ .......... 
515+ .......... 
516+ .......... 
517+ .......... 
518+ .......... 
519+ .......... 
520+ .......... 
521+ ..........` 
522+ 
523+ let  snake  =  [ {  x : 4 ,  y : 5  } ,  {  x : 3 ,  y : 5  } ,  {  x : 2 ,  y : 5  } ] 
472524let  direction  =  {  x : 1 ,  y : 0  } 
473525let  prevDirection  =  {  x : 1 ,  y : 0  } 
474526let  food_pos  =  {  x : 6 ,  y : 3  } 
@@ -483,11 +535,101 @@ let gameInterval = null
483535
484536setMap ( startScreenMap ) 
485537
538+ function  getSpeedMultiplier ( )  { 
539+   switch ( gameSettings . speed )  { 
540+     case  SPEEDS . LOW : return  0.7 
541+     case  SPEEDS . HIGH : return  1.4 
542+     default : return  1.0 
543+   } 
544+ } 
545+ 
546+ function  getThemeName ( )  { 
547+   return  gameSettings . theme  ===  THEMES . DARK  ? "Dark"  : "Light" 
548+ } 
549+ 
550+ function  getSpeedName ( )  { 
551+   switch ( gameSettings . speed )  { 
552+     case  SPEEDS . LOW : return  "Low" 
553+     case  SPEEDS . HIGH : return  "High" 
554+     default : return  "Medium" 
555+   } 
556+ } 
557+ 
558+ function  applyTheme ( )  { 
559+   if  ( gameSettings . theme  ===  THEMES . DARK )  { 
560+     // Fill background with dark tiles 
561+     for  ( let  x  =  0 ;  x  <  10 ;  x ++ )  { 
562+       for  ( let  y  =  0 ;  y  <  10 ;  y ++ )  { 
563+         const  sprites  =  getTile ( x ,  y ) 
564+         let  hasDarkBg  =  false 
565+         sprites . forEach ( sprite  =>  { 
566+           if  ( sprite . type  ===  dark_bg )  { 
567+             hasDarkBg  =  true 
568+           } 
569+         } ) 
570+         if  ( ! hasDarkBg  &&  ! sprites . some ( s  =>  s . type  ===  wall ) )  { 
571+           addSprite ( x ,  y ,  dark_bg ) 
572+         } 
573+       } 
574+     } 
575+   }  else  { 
576+     // Remove dark background tiles 
577+     for  ( let  x  =  0 ;  x  <  10 ;  x ++ )  { 
578+       for  ( let  y  =  0 ;  y  <  10 ;  y ++ )  { 
579+         const  sprites  =  getTile ( x ,  y ) 
580+         sprites . forEach ( sprite  =>  { 
581+           if  ( sprite . type  ===  dark_bg )  { 
582+             sprite . remove ( ) 
583+           } 
584+         } ) 
585+       } 
586+     } 
587+   } 
588+ } 
589+ 
590+ function  clearTileAndReapplyTheme ( x ,  y )  { 
591+   clearTile ( x ,  y ) 
592+   if  ( gameSettings . theme  ===  THEMES . DARK )  { 
593+     const  sprites  =  getTile ( x ,  y ) 
594+     const  hasWall  =  sprites . some ( s  =>  s . type  ===  wall ) 
595+     if  ( ! hasWall )  { 
596+       addSprite ( x ,  y ,  dark_bg ) 
597+     } 
598+   } 
599+ } 
600+ 
601+ function  showSettingsScreen ( )  { 
602+   gameState  =  GAME_STATE . SETTINGS 
603+   setMap ( settingsScreenMap ) 
604+   applyTheme ( ) 
605+   
606+   clearText ( ) 
607+   addText ( "SETTINGS" ,  {  x : 2 ,  y : 2 ,  color : color `7`  } ) 
608+   
609+   // Show theme option 
610+   const  themeColor  =  settingsMenu . selectedOption  ===  0  ? color `4`  : color `0` 
611+   addText ( `> Theme: ${ getThemeName ( ) }  ` ,  {  x : 1 ,  y : 5 ,  color : themeColor  } ) 
612+   
613+   // Show speed option 
614+   const  speedColor  =  settingsMenu . selectedOption  ===  1  ? color `4`  : color `0` 
615+   addText ( `> Speed: ${ getSpeedName ( ) }  ` ,  {  x : 1 ,  y : 7 ,  color : speedColor  } ) 
616+   
617+   addText ( "W/S: Navigate" ,  {  x : 2 ,  y : 10 ,  color : color `9`  } ) 
618+   addText ( "A/D: Change" ,  {  x : 2 ,  y : 11 ,  color : color `9`  } ) 
619+   addText ( "I: Back" ,  {  x : 2 ,  y : 12 ,  color : color `9`  } ) 
620+ } 
621+ 
486622function  showStartScreen ( )  { 
623+   gameState  =  GAME_STATE . START_SCREEN 
624+   setMap ( startScreenMap ) 
625+   applyTheme ( ) 
626+   
487627  clearText ( ) 
488628  addText ( "Simply just" ,  {  x : 4 ,  y : 2 ,  color : color `0`  } ) 
489629  addText ( "SNAKE" ,  {  x : 4 ,  y : 4 ,  color : color `7`  } ) 
490-   addText ( "Press I!" ,  {  y : 14 ,  color : color `4`  } ) 
630+   addText ( "Press I!" ,  {  y : 12 ,  color : color `4`  } ) 
631+   addText ( "Press K for" ,  {  y : 13 ,  color : color `6`  } ) 
632+   addText ( "settings!" ,  {  y : 14 ,  color : color `6`  } ) 
491633
492634  addSprite ( 2 ,  5 ,  head_right ) 
493635  addSprite ( 1 ,  5 ,  body_horizontal ) 
@@ -496,7 +638,8 @@ function showStartScreen() {
496638} 
497639
498640function  calculateSpeed ( score )  { 
499-   const  newSpeed  =  BASE_SPEED  -  ( score  *  SPEED_DECREASE_PER_POINT ) 
641+   const  baseSpeed  =  BASE_SPEED  *  getSpeedMultiplier ( ) 
642+   const  newSpeed  =  baseSpeed  -  ( score  *  SPEED_DECREASE_PER_POINT ) 
500643  return  Math . max ( newSpeed ,  MIN_SPEED ) 
501644} 
502645
@@ -629,7 +772,7 @@ function moveSnake() {
629772  // Check food collision 
630773  if  ( newHead . x  ===  food_pos . x  &&  newHead . y  ===  food_pos . y )  { 
631774    score ++ 
632-     clearTile ( food_pos . x ,  food_pos . y ) 
775+     clearTileAndReapplyTheme ( food_pos . x ,  food_pos . y ) 
633776    food_pos  =  generateFood ( ) 
634777    addSprite ( food_pos . x ,  food_pos . y ,  food ) 
635778    clearText ( ) 
@@ -654,29 +797,31 @@ function gameOver() {
654797    gameInterval  =  null 
655798  } 
656799
800+   playTune ( gameOverMelody ) 
801+ 
657802  clearText ( ) 
658803  addText ( "Game Over!" ,  {  y : 6 ,  color : color `3`  } ) 
659804  addText ( `Your Score: ${ score }  ` ,  {  y : 8 ,  color : color `6`  } ) 
660805  addText ( "I to restart!" ,  {  y : 10 ,  color : color `7`  } ) 
661- 
662-   playTune ( gameOverMelody ) 
806+   addText ( "K for menu!" ,  {  y : 11 ,  color : color `5`  } ) 
663807} 
664808
665809function  startGame ( )  { 
666810  gameState  =  GAME_STATE . PLAYING 
667-   snake  =  [ {  x : 4 ,  y : 5  } ] 
811+   snake  =  [ {  x : 4 ,  y : 5  } ,   {   x :  3 ,   y :  5   } ,   {   x :  2 ,   y :  5   } ] 
668812  direction  =  {  x : 1 ,  y : 0  } 
669813  prevDirection  =  {  x : 1 ,  y : 0  } 
670814  score  =  0 
671815  gameRunning  =  true 
672816
673-   currentSpeed  =  BASE_SPEED 
817+   currentSpeed  =  BASE_SPEED   *   getSpeedMultiplier ( ) 
674818
675819  if  ( gameInterval )  { 
676820    clearInterval ( gameInterval ) 
677821  } 
678822
679823  setMap ( levels [ level ] ) 
824+   applyTheme ( ) 
680825  food_pos  =  generateFood ( ) 
681826  addSprite ( food_pos . x ,  food_pos . y ,  food ) 
682827  drawSnake ( ) 
@@ -703,12 +848,41 @@ function checkDirectionChange(newDirection) {
703848  } 
704849} 
705850
851+ // Settings navigation 
852+ function  navigateSettings ( direction )  { 
853+   if  ( direction  ===  'up' )  { 
854+     settingsMenu . selectedOption  =  Math . max ( 0 ,  settingsMenu . selectedOption  -  1 ) 
855+   }  else  if  ( direction  ===  'down' )  { 
856+     settingsMenu . selectedOption  =  Math . min ( settingsMenu . options . length  -  1 ,  settingsMenu . selectedOption  +  1 ) 
857+   } 
858+   showSettingsScreen ( ) 
859+ } 
860+ 
861+ function  changeSettingValue ( direction )  { 
862+   if  ( settingsMenu . selectedOption  ===  0 )  {  // Theme 
863+     if  ( direction  ===  'left' )  { 
864+       gameSettings . theme  =  THEMES . LIGHT 
865+     }  else  if  ( direction  ===  'right' )  { 
866+       gameSettings . theme  =  THEMES . DARK 
867+     } 
868+   }  else  if  ( settingsMenu . selectedOption  ===  1 )  {  // Speed 
869+     if  ( direction  ===  'left' )  { 
870+       gameSettings . speed  =  Math . max ( 0 ,  gameSettings . speed  -  1 ) 
871+     }  else  if  ( direction  ===  'right' )  { 
872+       gameSettings . speed  =  Math . min ( 2 ,  gameSettings . speed  +  1 ) 
873+     } 
874+   } 
875+   showSettingsScreen ( ) 
876+ } 
877+ 
706878// Controls 
707879onInput ( "w" ,  ( )  =>  { 
708880  if  ( gameState  ===  GAME_STATE . PLAYING  &&  gameRunning  &&  direction . y  !==  1 )  { 
709881    const  newDirection  =  {  x : 0 ,  y : - 1  } 
710882    checkDirectionChange ( newDirection ) 
711883    direction  =  newDirection 
884+   }  else  if  ( gameState  ===  GAME_STATE . SETTINGS )  { 
885+     navigateSettings ( 'up' ) 
712886  } 
713887} ) 
714888
@@ -717,6 +891,8 @@ onInput("s", () => {
717891    const  newDirection  =  {  x : 0 ,  y : 1  } 
718892    checkDirectionChange ( newDirection ) 
719893    direction  =  newDirection 
894+   }  else  if  ( gameState  ===  GAME_STATE . SETTINGS )  { 
895+     navigateSettings ( 'down' ) 
720896  } 
721897} ) 
722898
@@ -725,6 +901,8 @@ onInput("a", () => {
725901    const  newDirection  =  {  x : - 1 ,  y : 0  } 
726902    checkDirectionChange ( newDirection ) 
727903    direction  =  newDirection 
904+   }  else  if  ( gameState  ===  GAME_STATE . SETTINGS )  { 
905+     changeSettingValue ( 'left' ) 
728906  } 
729907} ) 
730908
@@ -733,6 +911,8 @@ onInput("d", () => {
733911    const  newDirection  =  {  x : 1 ,  y : 0  } 
734912    checkDirectionChange ( newDirection ) 
735913    direction  =  newDirection 
914+   }  else  if  ( gameState  ===  GAME_STATE . SETTINGS )  { 
915+     changeSettingValue ( 'right' ) 
736916  } 
737917} ) 
738918
@@ -741,6 +921,16 @@ onInput("i", () => {
741921    startGame ( ) 
742922  }  else  if  ( gameState  ===  GAME_STATE . GAME_OVER )  { 
743923    restartGame ( ) 
924+   }  else  if  ( gameState  ===  GAME_STATE . SETTINGS )  { 
925+     showStartScreen ( ) 
926+   } 
927+ } ) 
928+ 
929+ onInput ( "k" ,  ( )  =>  { 
930+   if  ( gameState  ===  GAME_STATE . START_SCREEN )  { 
931+     showSettingsScreen ( ) 
932+   }  else  if  ( gameState  ===  GAME_STATE . GAME_OVER )  { 
933+     showStartScreen ( ) 
744934  } 
745935} ) 
746936
0 commit comments