Reputation: 540
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..).
Upvotes: 1
Views: 213
Reputation: 5884
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 ();
}
}
Upvotes: 1
Reputation: 1808
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.
Upvotes: 3