Light Cycles
Last changed: -83.67.113.237

.

Compile code with --define DIRECTX to play with XBox 360 controllers - you will need the February 2006 DirectX SDK installed.

    //-----------------------------------------------------------------------------
    // LightCycles.fs  Mini game using windows forms
    //
    // 2007 written by Phillip Trelford
    //-----------------------------------------------------------------------------


    #light


    #if DIRECTX // February 2006 DirectX SDK
    #R @"C:\WINDOWS\assembly\GAC_32\Microsoft.DirectX\2.0.0.0__31bf3856ad364e35\Microsoft.DirectX.dll" 
    open Microsoft.DirectX.XInput   // Required to read XBox 360 controllers
    #endif


    open System
    open System.Drawing
    open System.Windows.Forms


    /// Game states
    type GameState = | Start | Play | Over


    /// Form key handler type
    type KeyHandler (form:Form) =
        do form.KeyPreview <- true   
        let keys = Enum.GetValues (type Keys) :?> (Keys [])
        let keysDown = Array.create keys.Length false
        let FindKeyIndex code = keys |> Array.find_index (fun x -> code = x)
        do  form.KeyDown.Add    (fun e -> keysDown.[FindKeyIndex e.KeyCode] <- true)
        do  form.KeyUp.Add      (fun e -> keysDown.[FindKeyIndex e.KeyCode] <- false)   
        member this.IsKeyDown (keyCode:Keys) = keysDown.[FindKeyIndex keyCode]   
        member this.AnyKeyDown () = keysDown |> Array.exists (fun x -> x)    


    /// Player direction type
    type Direction = | Left | Right | Up | Down


    /// Player type
    type Player (color,startX,startY,direction,keys,keyHandler:KeyHandler) =
        let mutable x = startX
        let mutable y = startY
        let mutable d = direction


        member this.Color = color
        member this.X = x
        member this.Y = y
        member this.Keys = keys


        /// Reset player to start values
        member this.Reset () = x <- startX; y <- startY; d <- direction   


        /// Updates player position       
        member this.Update i =
            // Read keyborad
            let mutable newD = d
            let up, down, left, right = keys
            if keyHandler.IsKeyDown(up) then newD <- Up
            if keyHandler.IsKeyDown(down) then newD <- Down
            if keyHandler.IsKeyDown(left) then newD <- Left
            if keyHandler.IsKeyDown(right) then newD <- Right
    #if DIRECTX        
            // Read XBox 360 controller 
            let state = Controller.GetState(i)
            if state.IsConnected then
                let pad = state.GamePad
                if pad.UpButton then newD <- Up
                if pad.DownButton then newD <- Down
                if pad.LeftButton then newD <- Left
                if pad.RightButton then newD <- Right
    #endif            
            /// Don't allow suicide move
            match (d,newD) with
                | (Left, Right) | (Right, Left) | (Up, Down) | (Down, Up) -> ()
                | _ -> d <- newD   
            /// Update position with direction                         
            match d with
                | Up    -> y <- y - 1
                | Down  -> y <- y + 1
                | Left  -> x <- x - 1
                | Right -> x <- x + 1


    /// Main form         
    let form = new Form (Text="Light Cycles", Width=680, Height=544)       


    do  /// Layout for game window and status panel
        let layout = new TableLayoutPanel(Dock=DockStyle.Fill, ColumnCount = 2)        
        layout.ColumnStyles.Add( ColumnStyle(SizeType = SizeType.Percent, Width = 100.0f ) ) |> ignore
        layout.ColumnStyles.Add( ColumnStyle(SizeType = SizeType.Absolute, Width = 128.0f) ) |> ignore
        /// Play area in pixels
        let playArea = 500
        /// Game play area bitmap
        let bm = new Bitmap(playArea, playArea)
        /// Clears screen
        let ClearScreen () = 
            using (Graphics.FromImage(bm)) (fun graphics -> graphics.Clear(Color.Black))
        /// Draws text to screen
        let DrawText s =
            using (Graphics.FromImage(bm)) (fun graphics -> 
                let rect = new RectangleF(0.0f,0.0f,float32 playArea,float32 playArea)
                let align = new StringFormat(Alignment=StringAlignment.Center, LineAlignment=StringAlignment.Center)
                graphics.DrawString(s, form.Font, Brushes.White, rect, align)
            )    
        // Initialise screen        
        ClearScreen ()
        DrawText "Press any key to start"                    
        /// PictureBox to contain game bitmap
        let pictureBox = new PictureBox(Dock=DockStyle.Fill)
        pictureBox.Image <- bm    
        layout.Controls.Add(pictureBox)    


        let keyHandler = KeyHandler (form)


        /// Players array        
        let players = 
            [|  Player (Color.Red,playArea/2+20,playArea/2,Down,(Keys.Q,Keys.A,Keys.Z,Keys.X),keyHandler); 
                Player (Color.LightBlue,playArea/2-20,playArea/2,Up,(Keys.P,Keys.L,Keys.N,Keys.M),keyHandler)  |]
        players |> Array.iter (fun player -> bm.SetPixel(player.X,player.Y,player.Color))  


        /// Display player controls
        let statusPanel = new TableLayoutPanel(Dock=DockStyle.Fill, ColumnCount=1, BackColor=Color.DarkGray)
        players |> Array.iteri (fun i player ->
            let name = 
                [| ((new Label (Text=sprintf "Player %d" i, ForeColor=player.Color)) :> Control) |]
            let up, down, left, right = player.Keys
            let controls = 
                Array.combine [|"Up";"Down";"Left";"Right"|] [|up;down;left;right|]
                |> Array.map (fun (name,key) -> (new Label (Text=sprintf "%s '%O'" name key)) :> Control )
            Array.append name controls
            |> statusPanel.Controls.AddRange
        )
        layout.Controls.Add(statusPanel)
        form.Controls.Add(layout)        


        /// Game play - returns true if there has been a collision otherwise false
        let PlayGame () = 
            let collisions = players |> Array.mapi (fun i player -> 
                player.Update i
                let x, y = (player.X, player.Y)
                let wall = x < 0 || x >= playArea || y < 0 || y >= playArea
                if wall then
                    true
                else      
                    let bgColor = bm.GetPixel(x, y)                
                    bm.SetPixel (x, y, player.Color)
                    players |> Array.exists (fun player -> let c = player.Color in c.R = bgColor.R && c.G = bgColor.G && c.B = bgColor.B )              
            ) 
            pictureBox.Refresh ()


            match collisions |> Array.tryfind_index (fun x -> x = true) with
            | Some(i) -> i
            | None -> (-1)     


        /// Current game state
        let gameState = ref GameState.Start
        let gameOverWaitCount = ref 200
        let r = new Random()


        /// Timer instance
        let timer = new Timer()
        timer.Interval <- 1000/50
        // Timer event
        timer.Tick.Add (fun _ ->
            match !gameState with
            | Start ->
                if keyHandler.AnyKeyDown () then 
                    ClearScreen ()           
                    gameState := GameState.Play


            | Play -> 
                let i = PlayGame ()
                if i>=0 then                 
                    gameState := GameState.Over
                    gameOverWaitCount := 200
                    DrawText (sprintf "Game Over - Play %d Lost" i)
                    pictureBox.Refresh ()                                                
            | Over ->            
                // Shake screen
                form.Left <- form.Left + if !gameOverWaitCount > 150 then r.Next(5) - 2 else 0
                // Decrement Game Over wait
                decr gameOverWaitCount
                if !gameOverWaitCount <= 0 then                 
                    gameState := GameState.Start
                    players |> Array.iter (fun player -> player.Reset ())
                    ClearScreen ()
                    DrawText "Press any key to start"
                    pictureBox.Refresh ()                
        )   
        timer.Start ()


    [<STAThread>]    
    do Application.Run(form)