Home
JAQForum Ver 20.06
Log In or Join  
Active Topics
Local Time 04:25 29 Jan 2022 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : PicoMite 1.28" round display ideas challenge

     Page 1 of 2    
Author Message
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 5731
Posted: 10:29am 15 Dec 2021
Copy link to clipboard 
Print this post

Lewis recently sent me a 1.28" Round LCD HAT for Raspberry Pi Pico with a request to create a driver for it. This I have done and rather than add it to my huge box of unused displays we have agreed to send the display (not the cheapest) to the person who comes up with the most interesting idea for using it, with of course a commitment to building their idea.

Details of the display are in the link. The PCB is 37mm at its widest point and 58mm tall and of course includes the 5 position joystick switch. It plugs directly onto a standard Pico

Please post ideas on this thread and Lewis and I will judge the best with a Christmas deadline. Extra points will be given to anyone who models and demonstrates their idea on any other Pico display.


 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2089
Posted: 10:39am 15 Dec 2021
Copy link to clipboard 
Print this post

Ideal for some sort of 3D tunnel based vector shooter like Tempest, but I'm not committing to writing one .
CMM2 Welcome Tape, Creaky old text adventures
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 1055
Posted: 11:49am 15 Dec 2021
Copy link to clipboard 
Print this post

Idea: outdoor tool

Phase 1: magnetic sensor, create compass on LCD.
Phase 2: add GPS receiver, enter target coordinates and follow the pointer to target (combination of GPS and Magneto).

In fact you create a simple "Garmin GPS".

Volhout
If nothing goes right ... turn left
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 1055
Posted: 11:52am 15 Dec 2021
Copy link to clipboard 
Print this post

Second idea: game

Use the joystick to move the bullets



Very expensive solution, since these cost 25 cents in plastic .....
Edited 2021-12-15 21:53 by Volhout
If nothing goes right ... turn left
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2089
Posted: 12:52pm 15 Dec 2021
Copy link to clipboard 
Print this post

Proof of concept for Cylindrical Co-ordinate Snake.

I might have been able to implement it so far, but it's insane trying to get your head around the navigation to actually play it and I haven't quite worked out how to do the collision detection yet, but this is my entry (assuming I'm allowed) and I commit to finishing it if I win.

https://www.youtube.com/watch?v=IGKXw6mC5Ss

Warning very crappy unfinished code:

Option Base 0
Option Default None
Option Explicit On

Const HEIGHT% = 240
Const WIDTH% = 240
Const MAX_RADIUS! = 110.0

Const INCREASE% = 1, DECREASE% = 2, ANTICLOCKWISE% = 3, CLOCKWISE% = 4

Const SNAKE_SIZE% = 500

Dim radius! = 10
Dim theta! = 0
Dim theta_delta! = 1
Dim snake!(SNAKE_SIZE%, 1)
Dim head% = 200
Dim tail% = 0
Dim display%(240*240/64)

Cls
Circle MAX_RADIUS! + 10.0, MAX_RADIUS! + 10.0, MAX_RADIUS! + 10.0, 2, 1, Rgb(GREY)

Dim cmd%, x!, y!

Dim result%
Dim die% = 0
Dim oldx%, oldy%

On Key on_key()

Do

 oldx% = CInt(x!)
 oldy% = CInt(y!)

 y! = (Sin(Rad(90.0 - theta!)) * radius!)
 If theta! <= 180.0 Then
   x! = Sqr(Abs(radius!^2 - y!^2))
 Else
   x! = -Sqr(Abs(radius!^2 - y!^2))
 EndIf

 snake!(head%, 0) = x!
 snake!(head%, 1) = y!

 Box snake!(tail%, 0) + width% / 2, snake!(tail%, 1) + height% / 2, 4, 4, 1, Rgb(0,0,0), Rgb(0,0,0)
 Box snake!(head%, 0) + width% / 2, snake!(head%, 1) + height% / 2, 4, 4, 1, Rgb(255,0,0), Rgb(255,0,0)

 If oldx% <> CInt(x!) And oldy% <> CInt(y!) Then
   result% = set%(snake!(tail%, 0) + width% / 2, snake!(tail%, 1) + height% / 2)
 Else
   result% = 0
 EndIf
 unset(snake!(tail%, 0) + width% / 2, snake!(tail%, 1) + height% / 2)
 Inc die%, result%
'  If die% = 2 Then End
 If result% = 0 Then die% = 0

 Inc head%
 head% = head% Mod SNAKE_SIZE%
 Inc tail%
 tail% = tail% Mod SNAKE_SIZE%

 Pause 10
 If theta_delta! > 0.0 Then
   Inc theta!, theta_delta!
   If theta! >= 360.0 Then theta! = 0.0
 ElseIf theta_delta! < 0.0 Then
   Inc theta!, theta_delta!
   If theta! <= 0.0 Then theta! = 360.0
 EndIf

 If cmd% = INCREASE% Then
   Inc radius!, 3
 ElseIf cmd% = DECREASE% Then
   Inc radius!, -3
 ElseIf cmd%= CLOCKWISE% Then
   If theta_delta! = 1 Then
     theta_delta! = -1
     Inc radius!, 3
   Endif
 ElseIf cmd%= ANTICLOCKWISE% Then
   If theta_delta! = -1 Then
     theta_delta! = 1
     Inc radius!, 3
   Endif
 EndIf
 If cmd% <> 0 Then cmd% = 0

 radius! = Min(Max(radius!, 3.0), MAX_RADIUS!)

Loop

Function set%(x%, y%)
 Local bit% = y% * 240 + x%
 Local byte% = bit% \ 8
 bit% = bit% Mod 8
 Local value% = Peek(Var display%(), byte%)
 If value% And (1 << bit%) Then set% = 1
 value% = value% Or (1 << bit%)
 Poke Var display%(), byte%, value%
End Function

Sub unset(x%, y%)
 Local bit% y% * 240 + x%
 Local byte% = bit% \ 8
 bit% = bit% Mod 8
 Local value% = Peek(Var display%(), byte%)
 value% = value% Xor (1 << bit%)
 Poke Var display%(), byte%, value%
End Sub

Sub on_key()
 Local k$
 k$ = Inkey$
 Select Case k$
   Case "a" : cmd% = INCREASE%
   Case "z" : cmd% = DECREASE%
   Case "," : cmd% = ANTICLOCKWISE%
   Case "." : cmd% = CLOCKWISE%
 End Select
End Sub


Best wishes,

Tom
Edited 2021-12-15 23:04 by thwill
CMM2 Welcome Tape, Creaky old text adventures
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 1055
Posted: 01:31pm 15 Dec 2021
Copy link to clipboard 
Print this post

Wauw...

I haven't even finished thinking about the idea's, and you have finished the code.
I am impressed. You are just as fast as Peter.... Think of it ... its done ...

Difficult to play with a constant angular velocity.

Volhout
If nothing goes right ... turn left
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2089
Posted: 02:04pm 15 Dec 2021
Copy link to clipboard 
Print this post

  thwill said  I haven't quite worked out how to do the collision detection yet.


The secret is to avoid cartesian and do everything in cylindrical co-ordinates except for the actual screen plotting. I'd appreciate it if nobody "finished" this for me because I think it's a neat idea and would like to pursue it myself.

  Volhout said  I am impressed. You are just as fast as Peter.... Think of it ... its done ...


Thank you, high praise indeed.

  Quote  Difficult to play with a constant angular velocity.


I believe "Challenging" is the word to put on the marketing material.

Best wishes,

Tom
CMM2 Welcome Tape, Creaky old text adventures
 
Calli
Newbie

Joined: 20/10/2021
Location: Germany
Posts: 38
Posted: 09:10am 17 Dec 2021
Copy link to clipboard 
Print this post

I would do a small dart target with it... Oh wait...

I wish I had a simulator cockpit...

C!
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2089
Posted: 09:25am 17 Dec 2021
Copy link to clipboard 
Print this post

Attach a motion sensor to it and create a virtual, if 2D, snow globe ... doesn't make any use of the joystick though.

Best wishes,

Tom
CMM2 Welcome Tape, Creaky old text adventures
 
Mixtel90

Guru

Joined: 05/10/2019
Location: United Kingdom
Posts: 1529
Posted: 09:45am 17 Dec 2021
Copy link to clipboard 
Print this post

Oh, let's see...

A Vertigo record label simulator, using the joystick for variable speed, direction and sickness level.

No. Maybe not.  :)


I know! A pocket Interocitor! I might have a severe problem with the implementation though as some components are out of stock everywhere:
bead condenser (model # AB-619),
cathermin tube with an indium complex of +4,
intensifier disk.

:(
-- Mick

Zilog Inside! nascom.info for Nascom & Gemini
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2089
Posted: 11:19pm 18 Dec 2021
Copy link to clipboard 
Print this post

It is of course Polar-Coordinate "Snake", not Cylindical-Coordinate "Snake", the latter really would be mad .

Pretty playable now, though some graphical glitches to work out:

   https://youtu.be/wFxldIa4hpc

And the code:
' Copyright (c) 2021 Thomas Hugo Williams
' License MIT <https://opensource.org/licenses/MIT>
' For MMBasic 5.07

Option Base 0
Option Default None
Option Explicit On

Const VERSION$ = "0.2"
Const IS_CMM2% = Left$(Mm.Device$, 17) = "Colour Maximite 2"

If IS_CMM2% Then
 Option Console Serial
 Mode 7
EndIf

' Save "polar-snake.bas"

Const X_OFFSET% = MM.HRes \ 2
Const Y_OFFSET% = MM.VRes \ 2
Const HEIGHT% = 240
Const WIDTH% = 240
Const MAX_RADIUS% = Y_OFFSET% - 5

Const OUTWARD% = 0, CLOCKWISE% = 1, INWARD% = 2, ANTICLOCKWISE% = 3
Const UP% = 5, DOWN% = 6, LEFT% = 7, RIGHT% = 8, FIRE% = 9

Const MAX_SNAKE_SIZE% = 1000

Dim score%
Dim lives% = 3
Dim radius% = MAX_RADIUS% \ 2
Dim theta% = 0
Dim snake%(MAX_SNAKE_SIZE%, 1)
Dim head% = 20
Dim tail% = 0
Dim collision%(360 * MAX_RADIUS% / 64)
Dim direction%
Dim cmd%
Dim result%
Dim apple_x%, apple_y%
Dim snake_x%, snake_y%

On Key on_key()

Const FRAME_DURATION% = 10
Dim next_frame% = Timer + FRAME_DURATION%

Cls
Circle X_OFFSET%, Y_OFFSET%, MAX_RADIUS% + 5, 2, 1, RGB(GREY)

wipe()
show_text("POLAR SNAKE")
Pause 2000
wipe()

Do While lives% > 0
 start_round()

 Do
   snake%(head%, 0) = radius%
   snake%(head%, 1) = theta%
   to_cartesian(radius%, theta%, snake_x%, snake_y%)

   eat_apple()
   draw_snake()

   If set%(radius%, theta%, 1) = 1 Then
     die()
     Exit Do
   EndIf

   result% = set%(snake%(tail%, 0), snake%(tail%, 1))

   move_snake()
   check_direction()
   If apple_x% = -999 Then new_apple() Else eat_apple()

   ' Wait for next "frame".
   Do While Timer < next_frame% : Loop
   Inc next_frame%, FRAME_DURATION%
 Loop

Loop

game_over()

End

Sub start_round()
 Local i%
 radius% = MAX_RADIUS% \ 2
 theta% = 0
 For i% = 0 To Bound(snake%(), 1)
   snake%(i%, 0) = 0
   snake%(i%, 1) = 0
 Next i%
 For i% = 0 To Bound(collision%(), 1)
   collision%(i%) = 0
 Next
 direction% = CLOCKWISE%
 cmd% = 0
 apple_x% = -999
 apple_y% = -999

 Circle X_OFFSET%, Y_OFFSET%, MAX_RADIUS% + 5, 2, 1, RGB(GREY)
 show_text("LIVES: " + Str$(lives%))
 Pause 2000
 wipe()

 next_frame% = Timer + FRAME_DURATION%
End Sub

Sub die()
 Circle snake_x% + X_OFFSET%, snake_y% + Y_OFFSET%, 2, 1, 1, RGB(255,255,0), RGB(255,255,0)
 Pause 500
 Circle snake_x% + X_OFFSET%, snake_y% + Y_OFFSET%, 2, 1, 1, RGB(Black), RGB(Black)
 Inc lives%, -1
 wipe()
End Sub

Sub game_over()
 show_text("GAME OVER")
 Pause 2000
 wipe()
 show_text("SCORE: " + Str$(score%))
 Pause 2000
 wipe()
End Sub

Sub wipe()
 Local r%
 For r% = 10 To MAX_RADIUS% Step 5
   Circle X_OFFSET%, Y_OFFSET%, r% - 5, 5, 1, RGB(0,0,0)
   Circle X_OFFSET%, Y_OFFSET%, r%, 5, 1, RGB(255,0,0)
   Pause 50
 Next
 Circle X_OFFSET%, Y_OFFSET%, r% - 5, 5, 1, RGB(0,0,0)
End Sub

Sub show_text(s$)
 Text X_OFFSET%, Y_OFFSET%, s$, "CM", 1, 2, RGB(Red)
End Sub

Sub to_cartesian(r%, t%, x%, y%)
 Local yy! = Sin(Rad(90 - t%)) * r%
 y% = CInt(yy!)
 x% = Choice(t% <= 180, 1, -1) * CInt(Sqr(Abs(r%^2 - yy!^2)))
End Sub

Function set%(r%, t%, z%)
 Local t_% = Choice(r% = 0, 0, t%)

 Local bit% = r% * 360 + t_%
 Local byte% = bit% \ 8
 bit% = bit% Mod 8
 Local v% = Peek(Var collision%(), byte%)
 If v% And (1 << bit%) Then set% = 1
 If z% Then
   v% = v% Or (1 << bit%)
 Else
   v% = v% And Inv (1 << bit%)
 EndIf
 Poke Var collision%(), byte%, v%
End Function

Sub new_apple()
 If Rnd() < 0.8 Then Exit Sub
 Local r% = Rnd() * MAX_RADIUS%
 If r% < 40 Or Abs(radius% - r%) < 10 Then Exit Sub
 Local t% = Rnd() * 360
 If Abs(theta% - t%) < 10 Then Exit Sub
 to_cartesian(r%, t%, apple_x%, apple_y%)
 draw_apple()
End Sub

Sub eat_apple()
 If Abs(snake_x% - apple_x%) < 6 And Abs(snake_y% - apple_y%) < 6 Then
   erase_apple()
   apple_x% = -999
   Inc score%
   Local i%
   For i% = 1 To 10
     head% = (head% + 1) Mod MAX_SNAKE_SIZE%
     snake%(head%, 0) = radius%
     snake%(head%, 1) = theta%
   Next
 EndIf
End Sub

Sub draw_snake()
 Local x%, y%

 ' Erase tail.
 to_cartesian(snake%(tail%, 0), snake%(tail%, 1), x%, y%)
 Circle x% + X_OFFSET%, y% + Y_OFFSET%, 2, 1, 1, RGB(0,0,0), RGB(0,0,0)

 ' Draw head.
 to_cartesian(snake%(head%, 0), snake%(head%, 1), x%, y%)
 Circle x% + X_OFFSET%, y% + Y_OFFSET%, 2, 1, 1, RGB(255,0,0), RGB(255,0,0)

 Print radius%, theta%, x%, y%
End Sub

Sub move_snake()
 head% = (head% + 1) Mod MAX_SNAKE_SIZE%
 tail% = (tail% + 1) Mod MAX_SNAKE_SIZE%

 Select Case direction%
   Case OUTWARD%
     radius% = Min(radius% + 1, MAX_RADIUS% + 2)

   Case INWARD%
     Inc radius%, - 1
     If radius% < 0 Then
       radius% = -radius%
       theta% = (theta% + 180) Mod 360
       direction% = OUTWARD%
     EndIf

   Case CLOCKWISE%
     Inc theta%, -1 ' Correct this
     If theta% = -1 Then theta% = 359

   Case ANTICLOCKWISE%
     Inc theta%
     If theta% = 360 Then theta% = 0
 End Select
End Sub

Sub check_direction()
 If radius% <= 20 Then Exit Sub
 Select Case cmd%
   Case LEFT%
     Inc direction%, -1
     If direction% < 0 Then direction% = 3
   Case RIGHT%
     Inc direction%, 1
     If direction% > 3 Then direction% = 0
 End Select
 cmd% = 0
End Sub

Sub draw_apple()
 Local x% = apple_x% + X_OFFSET%
 Local y% = apple_y% + Y_OFFSET%
 Line x% - 4, y% - 5, x%, y%, 1, RGB(149,69,53)
 Line x% - 3, y% - 5, x% + 1, y%, 1, RGB(149,69,53)
 Circle x%, y%, 4, 1, 1, RGB(0,255,0), RGB(0,255,0)
End Sub

Sub erase_apple()
 Local x% = apple_x% + X_OFFSET%
 Local y% = apple_y% + Y_OFFSET%
 Line x% - 4, y% - 5, x%, y%, 1, RGB(0,0,0)
 Line x% - 3, y% - 5, x% + 1, y%, 1, RGB(0,0,0)
 Circle x%, y%, 4, 1, 1, RGB(0,0,0), RGB(0,0,0)
End Sub

Sub on_key()
 Select Case LCase$(Inkey$)
   Case "a" : cmd% = UP%
   Case "z" : cmd% = DOWN%
   Case "," : cmd% = LEFT%
   Case "." : cmd% = RIGHT%
   Case " " : cmd% = FIRE%
 End Select
End Sub


Should run on PicoMite and CMM2 as is, and I assume it will work on the PicoMite VGA Edition with only one or two changed lines.

Only two keys to play, "," and "." for turn left and right. Users of non-UK/US keyboards might want to change the key mapping in the on_key() subroutine.

Best wishes,

Tom
Edited 2021-12-19 09:26 by thwill
CMM2 Welcome Tape, Creaky old text adventures
 
Cyber

Senior Member

Joined: 13/01/2019
Location: Ukraine
Posts: 147
Posted: 06:19am 19 Dec 2021
Copy link to clipboard 
Print this post

  thwill said  It is of course Polar-Coordinate "Snake", not Cylindical-Coordinate "Snake", the latter really would be mad .

Pretty playable now, though some graphical glitches to work out:

   https://youtu.be/wFxldIa4hpc

This is cool!!
 
lew247

Guru

Joined: 23/12/2015
Location: United Kingdom
Posts: 1528
Posted: 08:20am 20 Dec 2021
Copy link to clipboard 
Print this post

  Volhout said  Second idea: game

Use the joystick to move the bullets



Very expensive solution, since these cost 25 cents in plastic .....


That looks awesome but I can't see anyone being able to simulate that on the Pico, especially with that graphic detail
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2089
Posted: 01:26pm 23 Dec 2021
Copy link to clipboard 
Print this post

Final (?) version of "Polar-coordinate Snake":

' Copyright (c) 2021 Thomas Hugo Williams
' License MIT <https://opensource.org/licenses/MIT>
' For MMBasic 5.07
' Music by Trevor Bailey

Option Base 0
Option Default None
Option Explicit On

Const VERSION$ = "1.0"
Const IS_CMM2% = Left$(MM.Device$, 17) = "Colour Maximite 2"

If IS_CMM2% Then
 Option Console Serial
 Mode 7
 Page Write 1
EndIf

' Save "polar-snake.bas"

Const X_OFFSET% = MM.HRes \ 2
Const Y_OFFSET% = MM.VRes \ 2
Const MAX_RADIUS% = Y_OFFSET% - 5
Const MAX_SNAKE_SIZE% = 1000
Const OUTWARD% = 0, CLOCKWISE% = 1, INWARD% = 2, ANTICLOCKWISE% = 3
Const UP% = 5, DOWN% = 6, LEFT% = 7, RIGHT% = 8, FIRE% = 9

' These would be constants but MMBasic does not support constant arrays
Dim SOUNDFX_NOTHING%(1) = (&hFFFFFFFFFFFFFFFF, &hFFFFFFFFFFFFFFFF)
Dim SOUNDFX_EAT%(1)     = (&hFFFFFFFFFF100C04, &hFFFFFFFFFFFFFFFF)
Dim SOUNDFX_DIE%(2)     = (&h0F10111213141516, &h0708090A0B0C0D0E, &hFF00010203040506)
Dim SOUNDFX_WIPE%(2)    = (&h0706050403020100, &h0F0E0D0C0B0A0908, &hFF16151413121110)

Dim score%
Dim lives% = 3
Dim difficulty% = 1
Dim game_type% = 1
Dim radius% = MAX_RADIUS% \ 2
Dim theta%
Dim snake%(MAX_SNAKE_SIZE%, 1)
Dim head%
Dim tail%
Dim collision%(360 * MAX_RADIUS% / 64)
Dim direction%
Dim cmd%
Dim apple_x%, apple_y%
Dim snake_x%, snake_y%
Dim frame_duration%
Dim next_frame%

Dim music_flag% = 1
Dim music_ptr% = Peek(CFunAddr music_data) + 4
Dim soundfx_flag% = 1
Dim soundfx_ptr% = Peek(VarAddr SOUNDFX_NOTHING%())

' Music and sound effects are played on SetTick interrupts.
SetTick 250, play_music, 1
SetTick 40, play_soundfx, 2

On Key on_key()

CLS
draw_border()

Do
 show_title()
 show_menu()
 init_game()
 Do While lives% > 0
   game_loop()
 Loop
 show_game_over()
 show_score()
Loop

End

Sub show_title()
 clear_display()
 Text X_OFFSET%, Y_OFFSET% - 15, "POLAR SNAKE", "CM", 1, 2, RGB(White)
 Text X_OFFSET%, Y_OFFSET% + 8, "(c) 2021 Thomas Hugo Williams", "CM", 7, 1, RGB(Red)
 Text X_OFFSET%, Y_OFFSET% + 20, "www.sockpuppetstudios.com", "CM", 7, 1, RGB(Red)
 Text X_OFFSET%, Y_OFFSET% + 40, "PRESS " + Choice(IS_CMM2%, "SPACE", "SELECT"), "CM", 1, 1, RGB(White)
 If IS_CMM2% Then Page Copy 1 To 0, B
 wait()
End Sub

Sub clear_display()
 Circle X_OFFSET%, Y_OFFSET%, MAX_RADIUS% + 3, 1, 1, RGB(BLACK), RGB(BLACK)
 draw_border()
End Sub

Sub draw_border()
 Circle X_OFFSET%, Y_OFFSET%, MAX_RADIUS% + 5, 2, 1, RGB(GREY)
End Sub

Sub wait(duration%)
 cmd% = 0
 If duration% = 0 Then
   Do While cmd% <> FIRE% : Loop
 Else
   Local expires% = Timer + duration%
   Do While Timer < expires% And cmd% <> FIRE% : Loop
 EndIf
End Sub

Sub show_menu()
 Const x% = X_OFFSET% - 45
 Local item% = 0, update% = 1

 clear_display()

 Text X_OFFSET%, Y_OFFSET% + 70, "Music by Trevor Bailey", "CM", 7, 1, RGB(Red)
 Text X_OFFSET%, Y_OFFSET% + 85, "Game Version " + VERSION$, "CM", 7, 1, RGB(Red)

 Do

   If update% Then
     Text x%, Y_OFFSET% - 65, "START GAME", , 1, 1, RGB(White)
     Text x%, Y_OFFSET% - 45, "DIFFICULTY: " + Str$(difficulty%), , 1, 1, RGB(White)
     Text x%, Y_OFFSET% - 25, "GAME TYPE:  " + Str$(game_type%), , 1, 1, RGB(White)
     Text x%, Y_OFFSET%  - 5, "MUSIC:    " + Choice(music_flag%, " ON", "OFF"), , 1, 1, RGB(White)
     Text x%, Y_OFFSET% + 15, "SOUND FX: " + Choice(soundfx_flag%, " ON", "OFF"), , 1, 1, RGB(White)
     Text x%, Y_OFFSET% + 35, "QUIT", , 1, 1, RGB(White)
     Text x% - 10, Y_OFFSET% - 65 + item% * 20, Chr$(137), , 1, 1, RGB(Red)
     If IS_CMM2% Then Page Copy 1 To 0, B
     Pause 100
     cmd% = 0
     update% = 0
   EndIf

   Select Case cmd%
     Case UP%
       If item% > 0 Then
         Text x% - 10, Y_OFFSET% - 65 + item% * 20, Chr$(137), , 1, 1, RGB(Black)
         Inc item%, -1
         update% = 1
       EndIf

     Case DOWN%
       If item% < 5 Then
         Text x% - 10, Y_OFFSET% - 65 + item% * 20, Chr$(137), , 1, 1, RGB(Black)
         Inc item%
         update% = 1
       EndIf

     Case LEFT%, RIGHT%, FIRE%
       Select Case item%
         Case 0
           If cmd% = FIRE% Then Exit

         Case 1 ' Difficulty
           Inc difficulty%, Choice(cmd% = LEFT%, -1, 1)
           difficulty% = Min(5, Max(difficulty%, 1))
           update% = 1

         Case 2 ' Game type
           Inc game_type%, Choice(cmd% = LEFT%, -1, 1)
           game_type% = Min(2, Max(game_type%, 1))
           update% = 1

         Case 3 ' Music
           music_flag% = Not music_flag%
           update% = 1

         Case 4 ' Sound FX
           soundfx_flag% = Not soundfx_flag%
           update% = 1

         Case 5 ' Quit
           If cmd% = FIRE% Then End

       End Select

     Case Else
       cmd% = 0

   End Select

   If update% = 1 Then start_soundfx(Peek(VarAddr SOUNDFX_EAT%()))

 Loop
End Sub

Sub init_game()
 lives% = 3
 score% = 0
 head% = 20
 tail% = 0
 frame_duration% = 5 + (6 - difficulty%)
End Sub

Sub game_loop()
 clear_display()
 Text X_OFFSET%, Y_OFFSET%, "LIVES: " + Str$(lives%), "CM", 1, 2, RGB(White)
 If IS_CMM2% Then Page Copy 1 To 0, B
 wait(2000)
 wipe()

 init_round()

 Do
   eat_apple()
   If apple_x% = -999 Then new_apple()
   draw_snake()
   If IS_CMM2% Then Page Copy 1 To 0, I
   If Not move_snake%() Then Exit
   check_direction()

   ' Wait for next "frame".
   Do While Timer < next_frame% : Loop
   Inc next_frame%, frame_duration%
 Loop

 die()
End Sub

Sub wipe()
 Local r%
 start_soundfx(Peek(VarAddr soundfx_wipe%()))
 For r% = 10 To MAX_RADIUS% Step 5
   Circle X_OFFSET%, Y_OFFSET%, r% - 5, 5, 1, RGB(0,0,0)
   Circle X_OFFSET%, Y_OFFSET%, r%, 5, 1, RGB(255,0,0)
   If IS_CMM2% Then Page Copy 1 To 0, B
   Pause 30
 Next
 clear_display()
 If IS_CMM2% Then Page Copy 1 To 0, B
End Sub

Sub init_round()
 Local i%
 radius% = MAX_RADIUS% \ 2
 theta% = 0
 For i% = 0 To Bound(snake%(), 1)
   snake%(i%, 0) = 0
   snake%(i%, 1) = 0
 Next i%
 snake%(head%, 0) = radius%
 snake%(head%, 1) = theta%
 to_cartesian(radius%, theta%, snake_x%, snake_y%)
 For i% = 0 To Bound(collision%(), 1)
   collision%(i%) = 0
 Next
 direction% = CLOCKWISE%
 cmd% = 0
 apple_x% = -999
 apple_y% = -999
 next_frame% = Timer + frame_duration%
End Sub

Sub to_cartesian(r%, t%, x%, y%)
 Local yy! = Sin(Rad(90 - t%)) * r%
 y% = Cint(yy!)
 x% = Choice(t% <= 180, 1, -1) * Cint(Sqr(Abs(r%^2 - yy!^2)))
End Sub

Sub eat_apple()
 If Abs(snake_x% - apple_x%) < 6 And Abs(snake_y% - apple_y%) < 6 Then
   start_soundfx(Peek(VarAddr SOUNDFX_EAT%()))
   draw_apple(0)
   apple_x% = -999
   Inc score%
   Local i%
   For i% = 1 To 10
     head% = (head% + 1) Mod MAX_SNAKE_SIZE%
     snake%(head%, 0) = radius%
     snake%(head%, 1) = theta%
   Next
 EndIf
End Sub

Sub draw_snake()
 Local x%, y%

 ' Erase tail.
 to_cartesian(snake%(tail%, 0), snake%(tail%, 1), x%, y%)
 Circle x% + X_OFFSET%, y% + Y_OFFSET%, 2, 1, 1, RGB(Black), RGB(Black)

 ' Redraw apple incase we have erased part of it.
 draw_apple(1)

 ' Draw head.
 Circle snake_x% + X_OFFSET%, snake_y% + Y_OFFSET%, 2, 1, 1, RGB(Red), RGB(Red)

'  Print radius%, theta%, snake_x%, snake_y%
End Sub

Sub die()
 start_soundfx(Peek(VarAddr soundfx_die%()))
 Local i% = head%, x%, y%
 Local sz% = Choice(head% > tail%, head% - tail%, head% + MAX_SNAKE_SIZE% - tail%)
 Local count%
 Do While i% <> tail%
   to_cartesian(snake%(i%, 0), snake%(i%, 1), x%, y%)
   Circle x% + X_OFFSET%, y% + Y_OFFSET%, 2, 1, 1, RGB(0,0,0), RGB(0,0,0)
   Inc i%, -1
   If i% < 0 Then i% = Bound(snake%(), 1)
   Inc count%
   If count% >= sz% \ 100 Then
     draw_border()
     If IS_CMM2% Then Page Copy 1 To 0, B
     count% = 0
   EndIf
 Loop

 ' Ensure last element of tail is deleted.
 to_cartesian(snake%(tail%, 0), snake%(tail%, 1), x%, y%)
 Circle x% + X_OFFSET%, y% + Y_OFFSET%, 2, 1, 1, RGB(0,0,0), RGB(0,0,0)
 If IS_CMM2% Then Page Copy 1 To 0, B

 ' Wait for the dying sound effect to complete.
 Do While Peek(Byte soundfx_ptr%) <> &hFF : Loop

 Inc lives%, -1
End Sub

Sub show_game_over()
 clear_display()
 Text X_OFFSET%, Y_OFFSET%, "GAME OVER", "CM", 1, 2, RGB(White)
 If IS_CMM2% Then Page Copy 1 To 0, B
 wait(5000)
End Sub

Sub show_score()
 clear_display()
 Text X_OFFSET%, Y_OFFSET%, "SCORE: " + Str$(score%), "CM", 1, 2, RGB(White)
 If IS_CMM2% Then Page Copy 1 To 0, B
 wait(5000)
End Sub

Function move_snake%()
 ' Clear the tail from the collision map.
 Local ignored% = set%(snake%(tail%, 0), snake%(tail%, 1), 0)

 head% = (head% + 1) Mod MAX_SNAKE_SIZE%
 tail% = (tail% + 1) Mod MAX_SNAKE_SIZE%

 Select Case direction%
   Case OUTWARD%
     radius% = radius% + 1
     If radius% > MAX_RADIUS% Then Exit Function

   Case INWARD%
     Inc radius%, - 1
     If radius% < 0 Then
       radius% = -radius%
       theta% = (theta% + 180) Mod 360
       direction% = OUTWARD%
     EndIf

   Case CLOCKWISE%
     Inc theta%, -1 ' Correct this
     If theta% = -1 Then theta% = 359

   Case ANTICLOCKWISE%
     Inc theta%
     If theta% = 360 Then theta% = 0
 End Select

 snake%(head%, 0) = radius%
 snake%(head%, 1) = theta%
 to_cartesian(radius%, theta%, snake_x%, snake_y%)

 ' Set the head, if it is already set then return 0.
 move_snake% = Not set%(radius%, theta%, 1)
End Function

Function set%(r%, t%, z%)
 Local t_% = Choice(r% = 0, 0, t%)

 Local bit% = r% * 360 + t_%
 Local byte% = bit% \ 8
 bit% = bit% Mod 8
 Local v% = Peek(Var collision%(), byte%)
 If v% And (1 << bit%) Then set% = 1
 If z% Then
   v% = v% Or (1 << bit%)
 Else
   v% = v% And INV (1 << bit%)
 EndIf
 Poke Var collision%(), byte%, v%
End Function

Sub check_direction()
 If radius% <= 20 Then Exit Sub
 If game_type% = 1 Then
   Select Case cmd%
     Case LEFT%
       Inc direction%, -1
       If direction% < 0 Then direction% = 3
     Case RIGHT%
       Inc direction%, 1
       If direction% > 3 Then direction% = 0
   End Select
 Else
   Select Case cmd%
     Case UP%
       direction% = OUTWARD%
     Case DOWN%
       direction% = INWARD%
     Case LEFT%
       direction% = ANTICLOCKWISE%
     Case RIGHT%
       direction% = CLOCKWISE%
   End Select
 EndIf
 cmd% = 0
End Sub

Sub new_apple()
 If Rnd() < 0.8 Then Exit Sub
 Local r% = Rnd() * MAX_RADIUS%
 If r% < 40 Or Abs(radius% - r%) < 10 Then Exit Sub
 Local t% = Rnd() * 360
 If Abs(theta% - t%) < 10 Then Exit Sub
 to_cartesian(r%, t%, apple_x%, apple_y%)
 draw_apple(1)
End Sub

Sub draw_apple(show%)
 If apple_x% = -999 Then Exit Sub
 Local x% = apple_x% + X_OFFSET%
 Local y% = apple_y% + Y_OFFSET%
 Local stalk% = Choice(show%, RGB(Brown), RGB(Black))
 Local apple% = Choice(show%, RGB(Green), RGB(Black))
 Line x% - 4, y% - 5, x%, y%, 1, stalk%
 Line x% - 3, y% - 5, x% + 1, y%, 1, stalk%
 Circle x%, y%, 4, 1, 1, apple%, apple%
End Sub

Sub on_key()
 Select Case LCase$(Inkey$)
   Case "a" : cmd% = UP%
   Case "z" : cmd% = DOWN%
   Case "," : cmd% = LEFT%
   Case "." : cmd% = RIGHT%
   Case " " : cmd% = FIRE%
 End Select
End Sub

' Called from interrupt to play next note of music.
Sub play_music()
 If music_flag% Then
   Local note% = Peek(Byte music_ptr%)
   If note% = &hFF Then
     music_ptr% = Peek(CFunAddr music_data) + 4
     note% = Peek(Byte music_ptr%)
   EndIf
   Play Sound 1, B, s, 440 * 2 ^ ((note% - 2) / 12.0), 15
   Inc music_ptr%
 Else
   Play Sound 1, B, O
 EndIf
End Sub

' Start a new sound effect.
Sub start_soundfx(ptr%)
 If Not soundfx_flag% Then Exit Sub

 ' Wait for current sound effect to end.
 Do While Peek(Byte soundfx_ptr%) <> &hFF : Loop

 soundfx_ptr% = ptr%

 ' Wait for first note of new sound effect to play.
 Do While soundfx_ptr% = ptr% : Loop
End Sub

' Called from interrupt to play next note of current sound effect.
Sub play_soundfx()
 If soundfx_flag% Then
   Local note% = Peek(Byte soundfx_ptr%)
   If note% < &hFF Then
     Play Sound 2, B, s, 440 * 2 ^ ((note% - 2) / 12.0)
     Inc soundfx_ptr%
   Else
     Play Sound 2, B, O
   EndIf
 Else
   Play Sound 2, B, O
 EndIf
End Sub

CSub music_data()
 00000000
 00000220 09000905 09000905 09000905 09000905 09000905 09000905 09000905
 09000905 18181815 1818181A 18181815 1818181A 15161816 13111516 15161816
 13111516 18181815 1518151A 18181815 1818181A 15161813 15131516 0C0E0C13
 0704000C 18181815 1818181A 18181815 1818181A 15161816 13111516 15161816
 13111516 18181815 1518151A 18181815 1818181A 15161813 15131516 091D0511
 0509051D 1515150E 15151516 1515150E 15151516 10131513 10131513 10131513
 00040013 1515150E 15151516 1515150E 15151516 13111013 15131115 16151316
 00000C18 18181815 1818181A 18181815 1818181A 15161816 13111516 15161816
 13111516 18181815 1518151A 18181815 1818181A 15161813 15131516 0C0E0C13
 0704000C 18181815 1818181A 18181815 1818181A 15161816 13111516 15161816
 13111516 18181815 1518151A 18181815 1818181A 15161813 15131516 091D0511
 0509051D 1515150E 15151516 1515150E 15151516 10131513 10131513 10131513
 00040013 1515150E 15151516 1515150E 15151516 13111013 15131115 16151316
 00000C18 18181815 1818181A 18181815 1818181A 15161816 13111516 15161816
 13111516 18181815 1518151A 18181815 1818181A 15161813 15131516 0C0E0C13
 0704000C 18181815 1818181A 18181815 1818181A 15161816 13111516 15161816
 13111516 18181815 1518151A 18181815 1818181A 15161813 15131516 091D0511
 0509051D FFFFFFFF
End CSub


Keys for menu are:
 [A] - up
 [Z] - down
 [<] - left
 [>] - right
 [Space] - select

Keys for game type 1 are:
 [<] - turn left
 [>] - turn right

Keys for game type 2 are:
 [A] - move outwards
 [Z] - move inwards
 [<] - move anti-clockwise
 [>] - move clockwise

Running on a PicoMite: https://youtu.be/LbC8pWRzu-4

Note that the PicoMite version does have audio, but mine isn't yet wired for sound ... TBH I'm a little suprised I didn't get an error about the fact that OPTION AUDIO is unset (Peter?)

Running on a Colour Maximite 2: https://youtu.be/quosIboidbM

Code will go up on GitHub when I have a moment.

Merry Christmas,

Tom
Edited 2021-12-23 23:34 by thwill
CMM2 Welcome Tape, Creaky old text adventures
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 1055
Posted: 02:36pm 23 Dec 2021
Copy link to clipboard 
Print this post

Wauw, you did it Tom !!!
This looks vey nice...

Volhout
If nothing goes right ... turn left
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 5731
Posted: 05:05pm 23 Dec 2021
Copy link to clipboard 
Print this post

After due consideration, the judges (Lewis and me) are happy to award the display to Tom (thwill). I'll get it in the post after Christmas.

Congrats to Tom for an inspired idea and implementation
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2089
Posted: 05:21pm 23 Dec 2021
Copy link to clipboard 
Print this post

Thank you Peter and Lewis, as you probably guessed I really wanted this device and really appreciate winning it.

I actually had a more original (game) idea but I was already half-way through the implementation of Polar-Snake. Hopefully I'll be able to follow-up on that in the New Year ... just need to knuckle down and deliver serial comms for MMB4L first.

Merry Christmas,

Tom
Edited 2021-12-24 03:22 by thwill
CMM2 Welcome Tape, Creaky old text adventures
 
lizby
Guru

Joined: 17/05/2016
Location: United States
Posts: 1872
Posted: 06:09pm 23 Dec 2021
Copy link to clipboard 
Print this post

Can you explain a little more about the mechanics of the game? I can't determine the difference between the circumstances in which the target appears to be eaten, and those in which it successfully moves away.
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2089
Posted: 11:05pm 23 Dec 2021
Copy link to clipboard 
Print this post

  lizby said  Can you explain a little more about the mechanics of the game? I can't determine the difference between the circumstances in which the target appears to be eaten, and those in which it successfully moves away.


Hi Lance, I had to scratch my head to work out what you were talking about . I think you are seeing something that isn't there. Whenever the head of the snake touches an apple (strictly speaking delta x and delta y between the two centers is less than 6 pixels) then the snake "eats" the apple, the apple never "moves away". What I think has confused you is that the (random) delay between one apple being eaten and the next appearing is not long enough, consequently if the two positions are close it looks like the apple may have moved - is that it ?

Merry Christmas,

Tom
CMM2 Welcome Tape, Creaky old text adventures
 
lizby
Guru

Joined: 17/05/2016
Location: United States
Posts: 1872
Posted: 12:33am 24 Dec 2021
Copy link to clipboard 
Print this post

Approximately. I thought the "3 lives" meant the game was over when the apple was eaten 3 times, but it apparently continues, but at some point the number of lives is reduced. I guess what I should have asked is "What constitutes a 'life'?"

(And shouldn't the snake be eating mice--what's an apple got to do with it? In the snake and apple story I'm familiar with, it's the humans who eat the apple, not the snake. Sorry, unmitigated pedantry.)
 
     Page 1 of 2    
Print this page
© JAQ Software 2022