Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

ReversiEight - A Windows 8 Reversi Game

, 22 May 2013
Describes the development of a Windows 8 Reversi game, covering the topics of UI design, the minimax algorithm and an interesting use for Linq.
ReversiEight.zip
ReversiEight
Assets
Background.jpg
BlackPiece.png
Board.png
GameOver.png
Logo.png
ReversiText.png
SmallLogo.png
SplashScreen.png
StoreLogo.png
WhitePiece.png
Common
Converters
Package.appxmanifest
Properties
ReversiEight_TemporaryKey.pfx
ViewModel
using ReversiEight.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace ReversiEight.ViewModel
{
  /// <summary>
  /// A view model that encapsulates the logic of the game of Reversi
  /// </summary>
  public class GameBoardViewModel : ViewModelBase
  {
    delegate void NavigationFunction(ref int row, ref int col);

    private static List<NavigationFunction> _navigationFunctions = new List<NavigationFunction>();

    static GameBoardViewModel()
    {
      _navigationFunctions.Add(delegate(ref int row, ref int col) { row++; });
      _navigationFunctions.Add(delegate(ref int row, ref int col) { row--; });
      _navigationFunctions.Add(delegate(ref int row, ref int col) { row++; col--; });
      _navigationFunctions.Add(delegate(ref int row, ref int col) { row++; col++; });
      _navigationFunctions.Add(delegate(ref int row, ref int col) { row--; col--; });
      _navigationFunctions.Add(delegate(ref int row, ref int col) { row--; col++; });
      _navigationFunctions.Add(delegate(ref int row, ref int col) { col++; });
      _navigationFunctions.Add(delegate(ref int row, ref int col) { col--; });
    }

    private List<GameBoardSquareViewModel> _squares;

    private BoardSquareState _nextMove = BoardSquareState.WHITE;

    private int _whiteScore = 0;

    private int _blackScore = 0;

    private bool _gameOver = false;

    public GameBoardViewModel(GameBoardViewModel copy)
    {
      _squares = copy._squares.Select(s => new GameBoardSquareViewModel(s.Row, s.Column, this)
      {
        State = s.State
      }).ToList();
      _nextMove = copy._nextMove;
    }

    public GameBoardViewModel()
    {
      _squares = new List<GameBoardSquareViewModel>();
      for (int col = 0; col < 8; col++)
      {
        for (int row = 0; row < 8; row++)
        {
          _squares.Add(new GameBoardSquareViewModel(row, col, this));
        }
      }

      InitialiseGame();
    }

    public bool GameOver
    {
      get { return _gameOver; }
      private set
      {
        SetField<bool>(ref _gameOver, value, "GameOver");
      }
    }

    public int BlackScore
    {
      get { return _blackScore; }
      private set
      {
        SetField<int>(ref _blackScore, value, "BlackScore");
      }
    }

    public int WhiteScore
    {
      get { return _whiteScore; }
      private set
      {
        SetField<int>(ref _whiteScore, value, "WhiteScore");
      }
    }

    public List<GameBoardSquareViewModel> Squares
    {
      get { return _squares; }
    }

    public BoardSquareState NextMove
    {
      get { return _nextMove; }
      private set
      {
        SetField<BoardSquareState>(ref _nextMove, value, "NextMove");
      }
    }

    public ICommand RestartGame
    {
      get
      {
        return new DelegateCommand(() => InitialiseGame());
      }
    }

    /// <summary>
    /// Makes the given move for the current player. Score are updated and play then moves
    /// to the next player.
    /// </summary>
    public void MakeMove(int row, int col)
    {
      // is this a valid move?
      if (!IsValidMove(row, col, NextMove))
        return;

      // set the square to its new state
      GetSquare(row, col).State = NextMove;

      // flip the opponents counters
      FlipOpponentsCounters(row, col, NextMove);
      
      // swap moves
      NextMove = InvertState(NextMove);

      // if this player cannot make a move, swap back again
      if (!CanPlayerMakeAMove(NextMove))
      {
        NextMove = InvertState(NextMove);
      }

      // check whether the game has finished
      GameOver = HasGameFinished();

      // update the scores
      BlackScore = _squares.Count(s => s.State == BoardSquareState.BLACK);
      WhiteScore = _squares.Count(s => s.State == BoardSquareState.WHITE);
    }

    /// <summary>
    /// Determines whether the given move is valid for the next turn
    /// </summary>
    public bool IsValidMove(int row, int col)
    {
      return IsValidMove(row, col, NextMove);
    }

    /// <summary>
    /// Determines whether the given move is valid
    /// </summary>
    public bool IsValidMove(int row, int col, BoardSquareState state)
    {
      // check the cell is empty
      if (GetSquare(row, col).State != BoardSquareState.EMPTY)
        return false;

      // if counters are surrounded in any direction, the move is valid
      return _navigationFunctions.Any(navFunction => MoveSurroundsCounters(row, col, navFunction, state));
    }

    /// <summary>
    /// Determines whether the given move 'surrounds' any of the opponents pieces.
    /// </summary>
    private bool MoveSurroundsCounters(int row, int column,
      NavigationFunction navigationFunction, BoardSquareState state)
    {
      int index = 1;

      var squares = NavigateBoard(navigationFunction, row, column);
      foreach(var square in squares)
      {
        BoardSquareState currentCellState = square.State;

        // the cell that is the immediate neighbour must be of the other colour
        if (index == 1)
        {
          if (currentCellState != InvertState(state))
          {
            return false;
          }
        }
        else
        {
          // if we have reached a cell of the same colour, this is a valid move
          if (currentCellState == state)
          {
            return true;
          }

          // if we have reached an empty cell - fail
          if (currentCellState == BoardSquareState.EMPTY)
          {
            return false;
          }
        }

        index++;
      }

      return false;
    }

    /// <summary>
    /// Flips all the opponents pieces that are surrounded by the given move.
    /// </summary>
    private void FlipOpponentsCounters(int row, int column, BoardSquareState state)
    {
      foreach (var navigationFunction in _navigationFunctions)
      {
        // are any pieces surrounded in this direction?
        if (!MoveSurroundsCounters(row, column, navigationFunction, state))
          continue;

        BoardSquareState opponentsState = InvertState(state);

        var squares = NavigateBoard(navigationFunction, row, column);
        foreach (var square in squares)
        {
          if (square.State == state)
            break;

          square.State = state;
        }
      }
    }

    private bool HasGameFinished()
    {
        return  !CanPlayerMakeAMove(BoardSquareState.BLACK) &&
                !CanPlayerMakeAMove(BoardSquareState.WHITE);
    }

    /// <summary>
    /// Determines whether there are any valid moves that the given player can make.
    /// </summary>
    private bool CanPlayerMakeAMove(BoardSquareState state)
    {
        // test all the board locations to see if a move can be made
        for (int row = 0; row < 8; row++)
        {
            for (int col = 0; col < 8; col++)
            {
                if (IsValidMove(row, col, state))
                {
                    return true;
                }
            }
        }
        return false;
    }

    private GameBoardSquareViewModel GetSquare(int row, int col)
    {
      return _squares.Single(s => s.Column == col && s.Row == row);
    }

    private BoardSquareState InvertState(BoardSquareState state)
    {
      return state == BoardSquareState.BLACK ? BoardSquareState.WHITE : BoardSquareState.BLACK;
    }

    /// <summary>
    /// A list of board squares that are yielded via the given navigation function.
    /// </summary>
    private IEnumerable<GameBoardSquareViewModel> NavigateBoard(NavigationFunction navigationFunction, int row, int column)
    {
      navigationFunction(ref column, ref row);
      while (column >= 0 && column <= 7 && row >= 0 && row <= 7)
      {
        yield return GetSquare(row, column);
        navigationFunction(ref column, ref row);
      }
    }

    /// <summary>
    /// Sets up the view model to the initial game state.
    /// </summary>
    private void InitialiseGame()
    {
      foreach (var square in _squares)
      {
        square.State = BoardSquareState.EMPTY;
      }

      GetSquare(3, 4).State = BoardSquareState.BLACK;
      GetSquare(4, 3).State = BoardSquareState.BLACK;
      GetSquare(4, 4).State = BoardSquareState.WHITE;
      GetSquare(3, 3).State = BoardSquareState.WHITE;

      NextMove = BoardSquareState.BLACK;

      WhiteScore = 0;
      BlackScore = 0;
    }
  }
}

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)

Share

About the Author

Colin Eberhardt
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.
 
I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.
 
I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.
 
Visit my blog - Colin Eberhardt's Adventures in .NET.
 
Follow me on Twitter - @ColinEberhardt
 
-
Follow on   Twitter   Google+

| Advertise | Privacy | Mobile
Web03 | 2.8.140821.2 | Last Updated 22 May 2013
Article Copyright 2013 by Colin Eberhardt
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid