Click here to Skip to main content
15,886,873 members
Articles / Multimedia / DirectX

DirectX Board Game Engine

Rate me:
Please Sign up or sign in to vote.
4.64/5 (19 votes)
29 Nov 2007CPOL11 min read 91.2K   1.7K   50  
An article on how to create a generic engine for board games such as Checkers or Chess
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using Bornander.Games.Direct3D;

namespace Bornander.Games.BoardGame.Direct3D
{
    /// <summary>
    /// This class is a panel that handles moving a piece around with the mouse and basic DirectX setup.
    /// </summary>
    public class GamePanel : Panel
    {

        #region Private members

        private IBoardGameLogic gameLogic;
        private IBoardGameModelRepository boardGameModelRepository;

        private Device device = null;
        private Camera camera;
        private Model selectedPieceModel;
        private Vector3 selectedPiecePosition;
        private VisualBoard board;
        private Move ponderedMove = null;
        private List<Move> availableMoves;
        private Point previousPoint = Point.Empty;

        private float cameraAngle = -((float)Math.PI / 2.0f);
        private float cameraElevation = 7.0f;
        private float cameraDistanceFactor = 1.5f;

        #endregion

        public GamePanel()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Helper method for setting present parameters for the DirectX initialization.
        /// </summary>
        private static PresentParameters CreatePresentParameters()
        {
            PresentParameters presentParameters = new PresentParameters();
            presentParameters.Windowed = true;
            presentParameters.SwapEffect = SwapEffect.Discard;
            presentParameters.EnableAutoDepthStencil = true;
            presentParameters.AutoDepthStencilFormat = DepthFormat.D16;
            return presentParameters;
        }

        /// <summary>
        /// Sets up a single light source and some ambient light. Helper method.
        /// </summary>
        /// 
        private static void SetupDefaultLight(Device device)
        {
            device.RenderState.Ambient = Color.FromArgb(0x606060);
            device.RenderState.Lighting = true;

            device.Lights[0].Enabled = false;
            device.Lights[0].Update();
            device.Lights[0].Type = LightType.Directional;
            device.Lights[0].Diffuse = Color.FromArgb(128, 128, 128);
            device.Lights[0].Direction = new Vector3(0.5f, -0.33f, 0.66f);
            device.Lights[0].Update();
            device.Lights[0].Enabled = true;
        }

        /// <summary>
        /// This methods iniitializes DirectX and the game.
        /// </summary>
        public void Initialize(IBoardGameLogic gameLogic, IBoardGameModelRepository boardGameModelRepository)
        {
            try
            {
                PresentParameters presentParameters = GamePanel.CreatePresentParameters();
                device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParameters);
                device.RenderState.ZBufferEnable = true;
                device.RenderState.Lighting = true;
                device.RenderState.ShadeMode = ShadeMode.Flat;

                GamePanel.SetupDefaultLight(device);

                // Has to do this or the directional light will go out if we resize the window
                device.DeviceReset += new EventHandler(OnDeviceReset);

                this.gameLogic = gameLogic;
                this.boardGameModelRepository = boardGameModelRepository;
                this.boardGameModelRepository.Initialize(device);

                // Create a camera and position in front of the board looking at the board center.
                // (Our board will occupie the area from 0,0,0 to 7,0,7)
                camera = new Camera(new Vector3(gameLogic.Columns / 2.0f, 5.0f, -gameLogic.Rows), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(gameLogic.Columns / 2 - 0.5f, 0.0f, gameLogic.Rows / 2 - 0.5f));


                // We must wait until the Direct3D device is created since the VisualBoard needs that to create models.
                board = new VisualBoard(gameLogic, boardGameModelRepository);
                availableMoves = gameLogic.GetAvailableMoves();

                SetCameraPosition();
            }
            catch (DirectXException exception)
            {
                MessageBox.Show(exception.Message, "Oops!");
            }
        }

        public void InitializeNewGame()
        {
            gameLogic.Initialize();
            availableMoves = gameLogic.GetAvailableMoves();
            Render();
        }

        private void OnDeviceReset(object sender, EventArgs e)
        {
            device.RenderState.ZBufferEnable = true;
            device.RenderState.Lighting = true;
            device.RenderState.ShadeMode = ShadeMode.Flat;

            GamePanel.SetupDefaultLight(device);

            Render();
        }

        /// <summary>
        /// Renders the screen, called whenever we need to redraw the screen
        /// </summary>
        public void Render()
        {
			device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.SystemColors.Control, 1.0f, 0);
            device.BeginScene();
            
            device.Transform.View = camera.ViewMatrix;
            device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)Width / (float)Height, 1.0f, 100.0f);

            // Render the board
            board.Render(device);

            // If a piece is selected, render that as well.
            if (selectedPieceModel != null)
            {
                selectedPieceModel.Position = selectedPiecePosition;
                selectedPieceModel.Render(device);
            }
	
			device.EndScene();
			device.Present();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            if (device != null)
                Render();
            else
                base.OnPaint(e);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            if (device == null)
                base.OnPaintBackground(e);
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // GamePanel
            // 
            this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.GamePanel_MouseMove);
            this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.GamePanel_MouseDown);
            this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.GamePanel_MouseUp);
            this.ResumeLayout(false);
        }

        private void CheckForGameOver()
        {
            // First check for game over, in case of game over display a message and exit the application.
            string gameOverStatement;
            if (gameLogic.IsGameOver(out gameOverStatement))
            {
                DialogResult result = MessageBox.Show(gameOverStatement + " Play another game?", "Game Over!", MessageBoxButtons.YesNo);
                if (result == DialogResult.Yes)
                {
                    gameLogic.Initialize();
                }
                else
                {
                    Application.Exit();
                }
            }
        }

        private void SetCameraPosition()
        {
            // Calculate a camera position, this is a radius from the center of the board and then cameraElevation up.
            float cameraX = gameLogic.Columns / 2.0f + (cameraDistanceFactor * gameLogic.Columns * (float)Math.Cos(cameraAngle));
            float cameraZ = gameLogic.Rows / 2.0f + (cameraDistanceFactor * gameLogic.Rows * (float)Math.Sin(cameraAngle));

            camera.Position = new Vector3(
                cameraX,
                cameraElevation,
                cameraZ);
        }

        public void HandleMouseWheel(object sender, MouseEventArgs e)
        {
            // If the user scroll the mouse wheel we zoom out or in
            cameraDistanceFactor = Math.Max(0.0f, cameraDistanceFactor + Math.Sign(e.Delta) / 5.0f);
            SetCameraPosition();
            Render();
        }

        private void GamePanel_MouseMove(object sender, MouseEventArgs e)
        {
            // Dragging using the right mousebutton moves the camera along the X and Y axis.
            if (e.Button == MouseButtons.Right)
            {
                cameraAngle += (e.X - previousPoint.X) / 100.0f;
                cameraElevation = Math.Max(0, cameraElevation + (e.Y - previousPoint.Y) / 10.0f);
                SetCameraPosition();
                previousPoint = e.Location;
            }

            Square square;
            if (e.Button == MouseButtons.Left)
            {
                if (ponderedMove != null)
                {
                    if (board.GetMouseOverBlockModel(device, e.X, e.Y, out square, ponderedMove.Destinations))
                    {
                        // Set the dragged pieces location to the current square
                        selectedPiecePosition.X = square.Column;
                        selectedPiecePosition.Z = square.Row;
                    }
                }
            }
            else
            {
                board.GetMouseOverBlockModel(device, e.X, e.Y, out square, GamePanel.GetSquaresFromMoves(availableMoves));
            }

            // Render since we might have moved the camera
            Render();
        }

        private void GamePanel_MouseDown(object sender, MouseEventArgs e)
        {
            // The previous point has to be set here or the distance dragged can be too big.
            previousPoint = e.Location;

            // If the mouse is over a block (see GetMouseOverBlockModel) for details on how detemining that
            // and the left button is down, try to grab the piece (if there is one at the square and it has valid moves).
            if (e.Button == MouseButtons.Left)
            {
                ponderedMove = null;
                Square square;
                if (board.GetMouseOverBlockModel(device, e.X, e.Y, out square, null))
                {
                    foreach (Move move in availableMoves)
                    {
                        // We have a move and it is started from the square we're over, start dragging a piece
                        if (square.Equals(move.Origin))
                        {
                            selectedPieceModel = board.PickUpPiece(square);
                            selectedPiecePosition = new Vector3(square.Column, 1.0f, square.Row);
                            ponderedMove = move;
                            break;
                        }
                    }
                }
            }
            Render();
        }

        private void GamePanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Square square;
                if (board.GetMouseOverBlockModel(device, e.X, e.Y, out square, null))
                {
                    // ponderedMove keeps track of the current potential move that will take place
                    // if we drop the piece onto a valid square, if ponderedMove is not null that means
                    // we're currently dragging a piece.
                    if (ponderedMove != null)
                    {
                        foreach (Square allowedSquare in ponderedMove.Destinations)
                        {
                            // Was it drop on a square that's a legal move?
                            if (square.Equals(allowedSquare))
                            {
                                // Move the piece to the target square
                                availableMoves = gameLogic.Move(ponderedMove.Origin, allowedSquare);
                                break;
                            }
                        }
                    }
                }
                board.DropPiece();
                selectedPieceModel = null;
                Render();

                CheckForGameOver();
            }
        }

        private static List<Square> GetSquaresFromMoves(List<Move> moves)
        {
            List<Square> squares = new List<Square>();
            foreach(Move move in moves)
            {
                squares.Add(move.Origin);
            }
            return squares;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Sweden Sweden
Article videos
Oakmead Apps Android Games

21 Feb 2014: Best VB.NET Article of January 2014 - Second Prize
18 Oct 2013: Best VB.NET article of September 2013
23 Jun 2012: Best C++ article of May 2012
20 Apr 2012: Best VB.NET article of March 2012
22 Feb 2010: Best overall article of January 2010
22 Feb 2010: Best C# article of January 2010

Comments and Discussions