简体   繁体   中英

How to use polymorphism instead of generic in C#

I have a generic base Interface IGameble (for the example move is generic) and Classes: Chess and Tic-Tac-Toe (and more board games) which derived from the generic Interface IGmable but are no longer generic.

I want to design class Agent (there might be several kind of these) that will play any kind of game (not in the same time), but the problem is that I cant

for example:

 interface IGame<T_move,T_matrix>
{
    T_matrix[,] game_state { get; }
    void MakeMove(Move mov);
    List<Move> ListAllMoves();
    ...// some more irrelevant code
}

class Tic_Tac_Toe : IGameble<Tuple<int,int>,int>
{
    public int turn;
    public int[,] game_state;
    public List<Tuple<int,int>> ListAllMoves()  {...}
    public void MakeMove(Tuple<int,int> mov) {...}
}
public Chess : IGameble <some other kind> //...
// so on more classes which uses other kind in the generic.

interface IAgent<Move>
{
    Move MakeMove();
}
public RandomAgent<T_Move,T_Matrix> : IAgent
{
   public IGameble<T_move> game;
   public D(IGameble game_) {game = game_} 
   public T_Move MakeMove() {//randomly select a move}
}
public UserAgent<T_Move,T_Matrix> : IAgent {get the move from the user}

The problems is that I want 1 instance of the random agent (or any other agent) to play all the games (1 game at a time) and using the generic enforce me to specifically choose the types of T_Move and T_Matrix that I want to use.

I might have it all wrong and used the generic poorly.

There was a suggestion to use IMovable and IBoardable instead of the generic. Is it the correct design? Would it solve all the problems?

I'm still a noobie in design pattern in C# :(

Also I will be appreciate it very much if some one can get a link to some design pattern that can help me here (if there is one..).

I think you can just use the is keyword:

public D(IA<T> a_) 
{
    if (a_ is B) 
    {
        //do something
    }
    else if (a_ is C)
    {
        //do something else
    }
}  

Generics in C# aren't nearly as flexible as C++, so as some commenters pointed out, you may not want to do things this way. You may want to design an intermediate interface, say IMove , that your algorithm can use to enumerate the moves in a generic way.

I don't know is that what you want, but you can do it without generics.

Sample code:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main (string[] args)
        {
            var game = new TicTacToe (
                (x) => new UserAgent (x, "John"),
                (x) => new RandomAgent (x));

            game.Play ();

            Console.ReadKey ();
        }
    }

    public interface IGame
    {
        IMove[] GetAvailableMoves ();
    }

    public interface IMove
    {
        void MakeMove ();
        string Description { get; }
    }

    public interface IAgent
    {
        string Name { get; }
        IMove SelectMove ();
    }

    public delegate IAgent AgentCreator (IGame game);

    public class RandomAgent : IAgent
    {
        private readonly IGame game;
        private readonly Random random = new Random ();

        public RandomAgent (IGame game)
        {
            this.game = game;
        }

        public string Name => "Computer (random moves)";

        public IMove SelectMove ()
        {
            var availableMoves = game.GetAvailableMoves ();

            int moveIndex = random.Next (availableMoves.Length);

            return availableMoves[moveIndex];
        }
    }

    public class UserAgent : IAgent
    {
        private readonly IGame game;

        public UserAgent (IGame game, string playerName)
        {
            this.game = game;
            Name = playerName;
        }

        public string Name { get; }

        public IMove SelectMove ()
        {
            var availableMoves = game.GetAvailableMoves ();

            Console.WriteLine ("Choose your move:");

            for (int i = 0; i < availableMoves.Length; i++)
            {
                Console.WriteLine (i + " " + availableMoves[i].Description);
            }

            int selectedIndex = int.Parse (Console.ReadLine ());

            return availableMoves[selectedIndex];
        }
    }

    public class TicTacToe : IGame
    {
        enum CellState { Empty = 0, Circle, Cross }

        CellState[] board = new CellState[9]; // 3x3 board
        int currentPlayer = 0;

        private IAgent player1, player2;

        public TicTacToe (AgentCreator createPlayer1, AgentCreator createPlayer2)
        {
            player1 = createPlayer1 (this);
            player2 = createPlayer2 (this);
        }

        public void Play ()
        {
            PrintBoard ();

            while (GetAvailableMoves ().Length > 0)
            {
                IAgent agent = currentPlayer == 0 ? player1 : player2;

                Console.WriteLine ($"{agent.Name} is doing a move...");

                var move = agent.SelectMove ();

                Console.WriteLine ("Selected move: " + move.Description);

                move.MakeMove (); // apply move

                PrintBoard ();

                if (IsGameOver ()) break;

                currentPlayer = currentPlayer == 0 ? 1 : 0;
            }
            Console.Write ("Game over. Winner is = ..."); // some logic to determine winner
        }

        public bool IsGameOver ()
        {
            return false;
        }

        public IMove[] GetAvailableMoves ()
        {
            var result = new List<IMove> ();

            for (int i = 0; i < 9; i++)
            {
                var cell = board[i];

                if (cell != CellState.Empty) continue;

                int index = i;

                int xpos = (i % 3) + 1;
                int ypos = (i / 3) + 1;

                var move = new Move ($"Set {CurrentPlayerSign} on ({xpos},{ypos})", () => 
                {
                    board[index] = currentPlayer == 0 ? CellState.Cross : CellState.Circle;
                });

                result.Add (move);
            }

            return result.ToArray ();
        }

        private char CurrentPlayerSign => currentPlayer == 0 ? 'X' : 'O';

        public void PrintBoard ()
        {
            Console.WriteLine ("Current board state:");

            var b = board.Select (x => x == CellState.Empty ? "." : x == CellState.Cross ? "X" : "O").ToArray ();

            Console.WriteLine ($"{b[0]}{b[1]}{b[2]}\r\n{b[3]}{b[4]}{b[5]}\r\n{b[6]}{b[7]}{b[8]}");
        }
    }

    public class Move : IMove // Generic move, you can also create more specified moves like ChessMove, TicTacToeMove etc. if required
    {
        private readonly Action moveLogic;

        public Move (string moveDescription, Action moveLogic)
        {
            this.moveLogic = moveLogic;
            Description = moveDescription;
        }

        public string Description { get; }

        public void MakeMove () => moveLogic.Invoke ();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM