/****************************************************************************/ /* */ /* snake - Snake Game for the NXT programmable brick. */ /* */ /* Copyright 2006 by R.Crawford */ /* */ /**************************************************************************** This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************/ #include "NXTDefs.h" #define oX 10 #define oY 30 #define mX 50 #define mY 30 /* ** Queue implementation */ #define KB_QLEN 10 #define KB_INIT(size) acquire kb_mutex \ arrinit kb_queue, 0, size \ set kb_len, size \ set kb_head, 0 \ set kb_tail, 0 \ release kb_mutex #define KB_PUSH(value) acquire kb_mutex \ mov kb_value, value \ call kb_push \ release kb_mutex #define KB_POP(dest) acquire kb_mutex \ call kb_pop \ mov dest, kb_value \ release kb_mutex dseg segment kb_queue byte[] kb_head byte kb_tail byte kb_value byte kb_len byte kb_mutex mutex dseg ends subroutine kb_push // increment before push add kb_head, kb_head, 1 brcmp LT, kb_push1, kb_head, kb_len set kb_head, 0 kb_push1: replace kb_queue, kb_queue, kb_head, kb_value // queue full? lose oldest value brcmp NEQ, kb_push2, kb_head, kb_tail add kb_tail, kb_tail, 1 brcmp LT, kb_push2, kb_tail, kb_len set kb_tail, 0 kb_push2: return ends subroutine kb_pop // queue empty? return 0 brcmp NEQ, kb_pop2, kb_head, kb_tail set kb_value, 0 return kb_pop2: // increment before pop add kb_tail, kb_tail, 1 brcmp LT, kb_pop1, kb_tail, kb_len set kb_tail, 0 kb_pop1: index kb_value, kb_queue, kb_tail return ends thread keyboard dseg segment tkb_flag byte[] tkb_rbArgs TReadButton tkb_prev byte dseg ends arrinit tkb_flag, 0, NO_OF_BTNS set tkb_rbArgs.Index, BTN2 tkb_loop: // Check for keypress add tkb_rbArgs.Index, tkb_rbArgs.Index, 1 mod tkb_rbArgs.Index, tkb_rbArgs.Index, NO_OF_BTNS syscall ReadButton, tkb_rbArgs brtst EQ, tkb_released, tkb_rbArgs.Pressed // pressed - check flag index tkb_prev, tkb_flag, tkb_rbArgs.Index brtst NEQ, tkb_released, tkb_prev // previously released, so push press event KB_PUSH(tkb_rbArgs.Index) PlayTone(TONE_A6,10) ResetSleepTimer tkb_released: replace tkb_flag, tkb_flag, tkb_rbArgs.Index, tkb_rbArgs.Pressed wait 10 jmp tkb_loop endt /* ** End Queue */ dseg segment aSnake TLocation[] delta TLocation apple TLocation appleScore byte length byte level byte levelmult byte lives byte waittime word nextscore word bGameOver byte bPressed byte bPaused byte score word sScore byte[] 'Score' sLevel byte[] 'Level' sLives byte[] 'Lives' sPause byte[] 'Pause' sAnother byte[] 'Another game?' sYes byte[] 'Yes' sNo byte[] 'No' sBye byte[] 'Goodbye!' sWelcome byte[] 'Welcome' sTo byte[] 'to' sTitle byte[] 'NXT Snake 0.1' dseg ends thread main precedes game, keyboard KB_INIT(KB_QLEN) TextOut(25,48,0,sWelcome) TextOut(40,40,0,sTo) TextOut(5,16,0,sTitle) wait 2000 endt thread game MainLoop: set level, 1 call SetLevelVars set lives, 3 set score, 0 set bPaused, FALSE LevelLoop: set bGameOver, FALSE call InitBoard GameLoop: call MoveSnake waitv waittime brtst EQ, GameLoop, bGameOver GameOver: call DrawBoard // Display last move call KillSnake sub lives, lives, 1 brtst GT, LevelLoop, lives call HiScore TextOut(10,50,1,sAnother) TextOut(20,30,0,sYes) TextOut(60,30,0,sNo) MainAsk: call WaitButton brcmp EQ, MainLoop, bPressed, BTN3 brcmp NEQ, MainAsk, bPressed, BTN2 TextOut(20,30,1,sBye) wait 1500 stop 1 endt subroutine Beep dseg segment PT_A TSoundPlayTone dseg ends set PT_A.Frequency, 440 set PT_A.Duration, 200 set PT_A.Loop, 0 set PT_A.Volume, 3 syscall SoundPlayTone, PT_A return ends subroutine Keypress KB_POP(bPressed) return ends subroutine WaitButton KB_INIT(KB_QLEN) wb_loop: KB_POP(bPressed) brtst EQ, wb_loop, bPressed return ends subroutine KillSnake dseg segment ka_size byte dseg ends ka_loop: arrsize ka_size, aSnake brcmp LTEQ, ka_rtn, ka_size, 1 sub ka_size, ka_size, 1 arrsubset aSnake, aSnake, 0, ka_size call DrawBoard wait 20 jmp ka_loop ka_rtn: return ends subroutine SetLevelVars dseg segment aScores word[] 0, 200, 500, 1000, 1500, 2500, 4000, 6500, 10000, 15000 dseg ends // multiplier = 10, 15, 20, ... mul levelmult, level, 5 add levelmult, levelmult, 5 // waittime div waittime, 4000, levelmult // nextscore index nextscore, aScores, level return ends subroutine InitBoard dseg segment tStart TLocation dseg ends // initialise array set tStart.X, 2+oX set tStart.Y, 2+oY mov length, levelmult arrinit aSnake, tStart, length // Clear keypress queue in case KB_INIT(KB_QLEN) // Initially move right set delta.X, 1 set delta.Y, 0 // Place first apple call PlaceApple return ends #define DATA_LEN 32 subroutine HiScore dseg segment THiScore struct level byte score word THiScore ends openArgs TFileOpen closeArgs TFileClose writeArgs TFileReadWrite readArgs TFileReadWrite deleteArgs TFileDelete sFilename byte[] 'snakehi.txt' sHighScore1 byte[] 'You have the' sHighScore2 byte[] 'highest score!' hs_scoredefault THiScore hs_score THiScore hs_tmpStr byte[] hs_err byte dseg ends set hs_scoredefault.level, 1 set hs_scoredefault.score, 0 mov openArgs.Filename, sFilename mov openArgs.Length, DATA_LEN syscall FileOpenRead, openArgs #pragma debuglog 'OpenRead:',openArgs.Result,':',openArgs.FileHandle brtst EQ, hs_readscore, openArgs.Result brcmp NEQ, hs_ioerror, openArgs.Result, LDR_FILENOTFOUND // File not found, so we must have a high score jmp hs_gothigh hs_readscore: mov readArgs.FileHandle, openArgs.FileHandle mov readArgs.Length, DATA_LEN syscall FileRead, readArgs #pragma debuglog 'Read:',readArgs.Result,':',readArgs.FileHandle,':',readArgs.Length brcmp EQ, hs_gotdata, readArgs.Result, LDR_ENDOFFILE brtst NEQ, hs_ioerror, readArgs.Result hs_gotdata: arrsubset readArgs.Buffer, readArgs.Buffer, 0, readArgs.Length set hs_err, 0 // not reset by unflatten! unflatten hs_score, hs_err, readArgs.Buffer, hs_scoredefault #pragma debuglog 'Level:',hs_score.level,',Score:',hs_score.score,',Err:',hs_err mov closeArgs.FileHandle, openArgs.FileHandle syscall FileClose, closeArgs #pragma debuglog 'Close:',closeArgs.Result,':',closeArgs.FileHandle brtst NEQ, hs_ioerror, closeArgs.Result brtst NEQ, hs_ioerror, hs_err brcmp LTEQ, hs_nothigh, score, hs_score.score mov deleteArgs.Filename, openArgs.Filename syscall FileDelete, deleteArgs #pragma debuglog 'Delete:',deleteArgs.Result,':',deleteArgs.Filename brtst NEQ, hs_ioerror, deleteArgs.Result hs_gothigh: syscall FileOpenWrite, openArgs #pragma debuglog 'OpenWrite:',openArgs.Result,':',openArgs.FileHandle brtst NEQ, hs_ioerror, openArgs.Result mov writeArgs.FileHandle, openArgs.FileHandle mov hs_score.level, level mov hs_score.score, score flatten writeArgs.Buffer, hs_score #pragma debuglog 'Flattened:',writeArgs.Buffer //mov writeArgs.Length, DATA_LEN arrsize writeArgs.Length, writeArgs.Buffer syscall FileWrite, writeArgs #pragma debuglog 'Write:',writeArgs.Result,':',writeArgs.FileHandle brtst NEQ, hs_ioerror, writeArgs.Result mov closeArgs.FileHandle, writeArgs.FileHandle syscall FileClose, closeArgs #pragma debuglog 'Close:',closeArgs.Result,':',closeArgs.FileHandle brtst NEQ, hs_ioerror, closeArgs.Result TextOut(18,48,1,sHighScore1) TextOut(10,40,0,sHighScore2) TextOut(30,24,0,sScore) TextOut(30,16,0,sLevel) NumOut(65,24,0,score) NumOut(65,16,0,level) PlayTone(TONE_C5,195) wait 400 PlayTone(TONE_C5,95) wait 100 PlayTone(TONE_C5,95) wait 100 PlayTone(TONE_E5,200) wait 200 PlayTone(TONE_C5,200) wait 200 PlayTone(TONE_E5,200) wait 200 PlayTone(TONE_G5,600) wait 1000 hs_nothigh: wait 500 // Give them a chance to see the death return hs_ioerror: #pragma debuglog 'I/O Error' return ends subroutine PlaceApple dseg segment pa_tCheck TLocation pa_idx byte pa_mult byte dseg ends //Position pa_GetPos: Random(apple.X,mX-1) Random(apple.Y,mY-1) add apple.X, apple.X, oX+1 add apple.Y, apple.Y, oY+1 // Don't place on snake arrsize pa_idx, aSnake pa_Loop: sub pa_idx, pa_idx, 1 index pa_tCheck, aSnake, pa_idx brcmp NEQ, pa_Loop2, pa_tCheck.X apple.X brcmp NEQ, pa_Loop2, pa_tCheck.Y apple.Y jmp pa_GetPos pa_Loop2: brtst GT, pa_Loop, pa_idx //Score mul pa_mult, levelmult, 2 Random(appleScore, pa_mult) add appleScore, appleScore, levelmult return ends subroutine MoveSnake dseg segment aTemp TLocation[] tNew TLocation tCheck TLocation tlen byte ms_idx byte extralen byte dseg ends // Copy trailing segments sub tlen, length, 1 arrsubset aTemp, aSnake, 0, tlen // Calculate new direction call Keypress brcmp EQ, ms_right, bPressed, BTN2 brcmp EQ, ms_left, bPressed, BTN3 brcmp NEQ, ms_add, bPressed, BTN4 not bPaused, bPaused jmp ms_add ms_right: set bPaused, FALSE brtst EQ, ms_r2, delta.X neg delta.Y, delta.X set delta.X, 0 jmp ms_add ms_r2: mov delta.X, delta.Y set delta.Y, 0 jmp ms_add ms_left: set bPaused, FALSE brtst EQ, ms_l2, delta.Y neg delta.X, delta.Y set delta.Y, 0 jmp ms_add ms_l2: mov delta.Y, delta.X set delta.X, 0 jmp ms_add ms_add: brtst EQ, ms_add2, bPaused call DrawBoard return ms_add2: // Calc new segment index tNew, aSnake, 0 add tNew.X, tNew.X, delta.X add tNew.Y, tNew.Y, delta.Y // Check for wall hits brcmp LTEQ, ms_hit, tNew.X, oX brcmp LTEQ, ms_hit, tNew.Y, oY brcmp GTEQ, ms_hit, tNew.X, oX+mX brcmp GTEQ, ms_hit, tNew.Y, oY+mY // Check for snake hits arrsize ms_idx, aSnake ms_HitLoop: sub ms_idx, ms_idx, 1 index tCheck, aSnake, ms_idx brcmp NEQ, ms_HitLoop2, tCheck.X tNew.X brcmp NEQ, ms_HitLoop2, tCheck.Y tNew.Y jmp ms_hit ms_HitLoop2: brtst GT, ms_HitLoop, ms_idx // Check for apple hit brcmp NEQ, ms_build, apple.X tNew.X brcmp NEQ, ms_build, apple.Y tNew.Y // Add score, and increase length of snake add score, score, appleScore div extralen, appleScore, levelmult add length, length, extralen // Place new apple call PlaceApple PlayTone(TONE_G5,20) wait 20 PlayTone(TONE_G6,10) // Check if we've reached new level brcmp LT, ms_build, score, nextscore brcmp GTEQ, ms_build, level, 9 add level, level, 1 call SetLevelVars PlayTone(TONE_C5,30) wait 30 PlayTone(TONE_E5,30) wait 30 PlayTone(TONE_G5,30) // Add new segment ms_build: arrbuild aSnake, tNew, aTemp // And draw new board call DrawBoard return ms_hit: set bGameOver, TRUE PlayTone(TONE_E4,200) wait 200 PlayTone(TONE_AS3,200) wait 200 return ends /* ** DrawBoard - clears the screen and draws the current position */ subroutine DrawBoard dseg segment dlArgs TDrawLine dpArgs TDrawPoint db_idx byte dseg ends // First draw the grid set dlArgs.Options, 1 set dlArgs.StartLoc.X, 0+oX set dlArgs.StartLoc.Y, 0+oY set dlArgs.EndLoc.X, mX+oX set dlArgs.EndLoc.Y, 0+oY syscall DrawLine, dlArgs set dlArgs.Options, 0 set dlArgs.StartLoc.X, 0+oX set dlArgs.StartLoc.Y, mY+oY set dlArgs.EndLoc.X, mX+oX set dlArgs.EndLoc.Y, mY+oY syscall DrawLine, dlArgs set dlArgs.StartLoc.X, 0+oX set dlArgs.StartLoc.Y, 0+oY set dlArgs.EndLoc.X, 0+oX set dlArgs.EndLoc.Y, mY+oY syscall DrawLine, dlArgs set dlArgs.StartLoc.X, mX+oX set dlArgs.StartLoc.Y, 0+oY set dlArgs.EndLoc.X, mX+oX set dlArgs.EndLoc.Y, mY+oY syscall DrawLine, dlArgs // Draw the level TextOut(65,56,0,sLevel) NumOut(65,48,0,level) // Draw the score TextOut(65,32,0,sScore) NumOut(65,24,0,score) // Draw the lives TextOut(65,8,0,sLives) NumOut(65,0,0,lives) // Paused? brtst EQ, db_paused, bPaused TextOut(20,0,0,sPause) db_paused: // Draw the snake arrsize db_idx, aSnake db_Loop: sub db_idx, db_idx, 1 index dpArgs.Location, aSnake, db_idx set dpArgs.Options, 0 syscall DrawPoint, dpArgs brtst GT, db_Loop, db_idx // Draw the apple mov dpArgs.Location, apple syscall DrawPoint, dpArgs return ends