Code (this was my .clj) for the Music Game V1

[for the overview of the code & video of the game, go here]

[download the code file]

Remember: this is the WORST. CODE. EVER. This is my teach-your-self-Clojure first exercise, and I have absolutely no idea what I'm actually doing. Also, Java GUI.

UPDATED code April 1  8:25 PM to replace "def" with  "let" in several places. (thanks @maxkreminski)

 

UPDATED the post (not the code)  April 2 to add formatting 


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
(ns foolaround-app.core
  (:import
    (javax.swing JFrame JButton JOptionPane JPanel BorderFactory JLabel BoxLayout SwingUtilities SwingConstants)
    (javax.swing.border LineBorder)
    (javax.sound.midi MidiSystem Sequence MidiEvent ShortMessage Track)
    (java.awt Canvas Graphics Color Toolkit BorderLayout Font )
    (java.lang Thread)
    (java.awt.event ActionListener MouseAdapter))
  (:gen-class))

 

; vector of immutable  properties for sound and color of the "buttons"
; used by the 2 functions that play sound 

(def button_prop_COLOR [Color/BLUE Color/RED Color/GREEN Color/YELLOW Color/MAGENTA])
(def button_prop_noteAndInst [[106 48][106 52][106 55][85 40]])

 

; STATE THAT CHANGES IS HERE:
;---------------------------------------------------
;---------------------------------------------------
  
 (def button_queue (atom [])) 
 ; holds the button click events (as integers 1-3)


 (defn update_queue [numToAdd]
     
         (if (> numToAdd 0)
             (swap! button_queue conj numToAdd)
             (reset! button_queue [])
             )  ; close if
     ); close function

;---------------------------------------------------
;---------------------------------------------------

 


; ------------------CHECK USERS GUESS (clicks) ------

; as soon as a sequence plays, this starts looping (recursive)
; looking at the button click events, comparing to current actual game sequence

(defn check_guess [currSeq]
  
  ; make a copy/deref the button queue
  ; shorten it in case it's longer than the current Sequence 
  ; then see if the button queue matches the current sequence

  (let [copyOfButtonQueue (deref button_queue)
    seqCount (count currSeq)
    shorterButtonQueue (take seqCount copyOfButtonQueue)
    buttonQueueCount (count (vec shorterButtonQueue))
    userRight? (= shorterButtonQueue (subvec currSeq 0 buttonQueueCount))
    ]


 ; make the decision to either keep checking, start next longer sequence, or start over
 (cond 

   (and userRight? (= buttonQueueCount seqCount)) (+ 2 2) ; OMG AWFUL - this forces return of true
   ; user did full sequence, so ready for next note 

   (and (not userRight?) (> buttonQueueCount 0)) (println "user wrong")
   ; user hit a wrong button, which will start new game

   :else  (recur currSeq)
   ; keep checking, user not done w/ sequence but right so far so recall check_guess

  ) ; close cond
  ; if we get here, we return to main game loop with either nil or true
 
  ) ; close let                      
 ) ; close checkguess defn  

;---------------------------------------------------

 


;-------------------PLAY SOUND----------------------------------
; this function take a vector w/ MIDI note & instrument integers, the MIDI sequencer, and track
; it sets the instrument and plays the note
; the MIDI sequencer and track were created in main method


(defn play_sound [note_and_inst_vec player track]

  ; figure out where we are in the sequence, so we can add new events
  ; at the next "time" to be played

  (.stop player) ; this stops it even if it is not yet started... 


  ; define a bunch of local vars for the midi messages

  (let [
    currTickPosition (.getTickPosition player)
    noteToPlay (note_and_inst_vec 1)
    instrumentToPlay (note_and_inst_vec 0)

    instMessage (ShortMessage.)
    changeInstEvent (MidiEvent. instMessage currTickPosition) 

    noteonmessage (ShortMessage.)
    noteon (MidiEvent. noteonmessage currTickPosition)

    noteoffmessage (ShortMessage.)
    noteoff (MidiEvent. noteoffmessage (+ currTickPosition 12))
  ]
   
    (.setMessage instMessage 192 1 instrumentToPlay 0)
    (.add track changeInstEvent) 
        
     (.setMessage noteonmessage 144 1 noteToPlay 100) 
     (.add track noteon)
    
     (.setMessage noteoffmessage 128 1 noteToPlay 100) 
     (.add track noteoff)    


     ; now PLAY all messages we added to the track
       (.start player)
    (Thread/sleep 100)  ; attempt to control the timing between notes
   ); close let
  ) ; end playASound function

 


; ------------------LIGHT / COLOR THE BUTTONS -----------------
; just takes a button (which is actually just a 'panel', not UI button)
; and a color and changes the color, then changes back to original dark gray
; BAD: for now, I hard-coded the 'default' color state


(defn light_buttons [button color]
     
    (.setBackground button color)
    (.paintImmediately button 0 0 (.getWidth button) (.getHeight button))
        
      (Thread/sleep 160)  ; this determines how long the button "lights"
          
    (.setBackground button Color/darkGray)
    )
;---------------------------------------------------------------------

 

 

; ------------------MAIN GAME LOOP--------------------------------------
; most of the "game" is here -- it is recursive, it basically:
; -- play a sequence of notes
; -- then call check guess which keeps checking the user's event queue 
; -- based on check guess return, either add 1 note to sequence OR start over

(defn main_game_loop [buttonlist msg_display currentSequence player track]

   (Thread/sleep 500) ; aack everything fails if I don't do this pause here!

   ; calls the atom / changes the state---------------------------
   (update_queue 0) ; zero's out the list of action events from the user
   ; because each time we play a sequence, user has to start clicking again from the beginning
   

   ; this is awkward, but I'm too lazy to create a second JLabel to display the score
   (.setText msg_display (str "Playing new sequence. Score: "  (count currentSequence))) 


   ; add 1 to the current sequence of notes
   (def newSequence (conj currentSequence (+ (rand-int 3) 1)))

  
   ; now PLAY the sequence
   (doseq [sequencePart newSequence]  ; this is just an int for which button in buttonlist
    (do 
      (def buttonNum (- sequencePart 1)) ; this gives us the index into buttonlist 
                                         ; AND button property lists

      (def buttonToPlay (buttonlist buttonNum))  ; figure out which ACTUAL button to play
      (play_sound (button_prop_noteAndInst buttonNum) player track)
      (light_buttons buttonToPlay (button_prop_COLOR buttonNum))   

    ) ; close do
  ) ; close doseq

   (.setText msg_display (str "Now Your Turn. Score: "  (count currentSequence)))

   ; now start the checking user guess 'loop'
   (if 
     (nil? (check_guess newSequence))
        (do 
          ; user made a wrong move
          ; play an ugly sound, then start over
          (play_sound (button_prop_noteAndInst 3) player track)
      
          (.setText msg_display (str "SORRY. Final Score: "  (count currentSequence)))  ; must start over
          (Thread/sleep 2000)
          (.setText msg_display "Starting Over")
          ; (main_game_loop buttonlist msg_display [] player track) ; diff send empty sequence
          (recur buttonlist msg_display [] player track) ; diff send empty sequence
          )

        ; ELSE condition -- they answered correctly, recursively call this function again
        (recur buttonlist msg_display newSequence player track) ; something is weird now
        
    ) ; close if

 ) ; close function
;-------------------------

 

;------------------START GAME----------------------------------
; this is called only the very first time, NOT when user fails & new "game" starts again

(defn startgame [buttonlist msg_display player track]
  (.setText msg_display "Starting New Game")
  (Thread/sleep 2000)
  (.setText msg_display "Watch and listen!")
  (main_game_loop buttonlist msg_display [] player track)
  )
;--------------------------------------------------  

 


;--------------------MAIN JUST CONSTRUCTS GUI AND SEQUENCER --------------------------------

(defn -main
  
  [& args]
  (println "in main")
   
  ; define the MIDI then GUI variables
  (let [
        player (MidiSystem/getSequencer)
        seq (Sequence. Sequence/PPQ 10)
        track (.createTrack seq)

        frame  (JFrame. "testFrame")
        panel (JPanel.)
        msg_display (JLabel. "Starting Game" SwingConstants/RIGHT)
        

    ;made the buttons JPanels vs. JButtons because I don't want JButton UI behavior
        buttonOne (JPanel. )
        buttonTwo (JPanel. )
        buttonThree (JPanel. )


    ; put them in a list, which gets passed around
    buttonlist [buttonOne buttonTwo buttonThree]
    

        ;make just ONE button listener for *all* buttons
        ; it gets the source of the event (which button) then uses the button(panel's) name to determine *which* button was pushed
    ; I set the button names later in this function


        button_listener 
            (proxy [MouseAdapter] []
                (mousePressed [evt]
          (def buttonJustPushed (.getSource evt))
          
                    (def buttonNum (Integer/parseInt (.getName buttonJustPushed)))

          (play_sound (button_prop_noteAndInst (- buttonNum 1)) player track)

          (light_buttons buttonJustPushed (button_prop_COLOR (- buttonNum 1)))

          (update_queue buttonNum) ; update the atom with this event (integer)    
             

                    ))  ; closing the definition of button_listener as anon inner class
      ] ; closes the [] of let

      ; get the MIDI sequencer ready
      (.setSequence player seq)
      (.open player)


    ; JUST GUI stuff ----------------------

    ; register the one listener with each button
    ; set the name of the button (so we can tell which button was pushed, from the
    ; event Java hands to the event listener)
        
        (.addMouseListener buttonOne button_listener)
        (.addMouseListener buttonTwo button_listener)
        (.addMouseListener buttonThree button_listener)

    (.setName buttonOne "1")
    (.setName buttonTwo "2")
    (.setName buttonThree "3")

    (.setBackground buttonOne Color/darkGray)
    (.setBackground buttonTwo Color/darkGray)
    (.setBackground buttonThree Color/darkGray)

    (def myBorder (BorderFactory/createLineBorder Color/WHITE 12))

    (.setBorder buttonOne myBorder)
    (.setBorder buttonTwo myBorder)
    (.setBorder buttonThree myBorder)

    (.setSize buttonOne 200 200)
    (.setSize buttonTwo 200 200)
    (.setSize buttonThree 200 200)
                      
        ; we are still within the LET here 

    (def contentPane (.getContentPane frame))

    (.setFont msg_display (Font. Font/SANS_SERIF Font/BOLD 18))
    (.setBorder msg_display myBorder)

    (doto contentPane
          (.add BorderLayout/CENTER panel)
        
        (.add BorderLayout/NORTH msg_display)
      ); close doto

     (doto panel
        (.add buttonOne)
        (.add buttonTwo)
    (.add buttonThree)
    (.setSize 420 300)

        (.setBackground Color/GRAY)
        (.setLayout (BoxLayout. panel BoxLayout/Y_AXIS))
        ); close doto
     
        
    (doto frame
      (.setSize 550 500)
      (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
      (.setResizable false)
      
      (.setVisible true)
      ); close doto

  ;---------------END OF GUI BUILDING----------------------------
      
  ; call startgame and then we're done with main
      (startgame buttonlist msg_display player track)
          
    ) ; closes the entire let
    
  ) ; close main

;-------------------------