I made a simple battleship in C# console with a little AI ( just a little ) and The goal of this practice is improving my knowledge about OOP.
Game Rules:
1.You Attack To The Enemy then he Attacked You.
2.if you do not find any ships with the help of Hint, one of your own ships will sink
3.I Write a Simple Little Small Tiny AI. If Enemy can't find your ship in his turn he can search near and find the ships for next turn (if it is near the ship).
4.You and your enemy just have 5 ships.
First i put all of my code then explain about it.
My Program Class [Main Method]:
{
static void Main()
{
GameEngine myGameEngine = new GameEngine();
while (true)
{
myGameEngine.Engine();
}
}
}
it's very simple and clean.
Then gameEngine Class:
class GameEngine
{
List<char> _myIsland = new List<char>();
List<char> _enemyIsland = new List<char>();
List<char> _enemyIslandCover = new List<char>();
bool isGenerate;
int hint;
int cubePosition;
char saveCharacter;
private Ships ships;
enum Direction
{
Up = -10,
Left = -1,
Right = 1,
Down = 10
}
public GameEngine()
{
ships = new Ships(this, 5);
hint = 1;
}
public void DrawIslands()
{
int Counter = 0;
int HowManyTime = 0;
Console.Clear();
while (true)
{
HowManyTime += 10;
while (Counter < HowManyTime)
{
Console.Write("{0} ", _myIsland[Counter]);
Counter++;
}
Console.Write("║ ");
Counter -= 10;
while (Counter < HowManyTime)
{
Console.Write("{0} ", _enemyIslandCover[Counter]);
Counter++;
}
Console.WriteLine();
if (HowManyTime == 100)
{
break;
}
}
}
private void ShowInformation()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("░ YOUR SHIPS: {0} \t ENEMY's SHIP: {1} \t HINT: {2}", ships.YourShip, ships.EnemyShip, hint);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("░ 1. ATTACK \t 2. HINT \t 3. EXIT");
Console.ForegroundColor = ConsoleColor.White;
}
public void Engine()
{
if (!isGenerate)
{
FirstTimeGenerate();
}
DrawIslands();
ShowInformation();
string userInput = GetString("░ Choose a number > ");
switch (userInput)
{
case "1":
cubePosition = 0;
saveCharacter = _enemyIslandCover[cubePosition];
_enemyIslandCover[cubePosition] = '■';
DrawIslands();
bool isEnter = false;
while (!isEnter)
{
ConsoleKeyInfo readKey = Console.ReadKey();
switch (readKey.Key)
{
case ConsoleKey.UpArrow:
Move(Direction.Up);
break;
case ConsoleKey.DownArrow:
Move(Direction.Down);
break;
case ConsoleKey.RightArrow:
Move(Direction.Right);
break;
case ConsoleKey.LeftArrow:
Move(Direction.Left);
break;
case ConsoleKey.Enter:
isEnter = true;
bool result = ships.Attack(_enemyIsland, _enemyIslandCover , cubePosition);
if (result)
{
GetSucces("You Sink One of the Enemy's Ship!! POWER");
if (hint == 0)
{
hint++;
}
HasAnyoneWon();
}
else
{
GetError("DAMN IT ADMIRAL!!!");
}
break;
}
}
ships.EnemyAttack(_myIsland);
HasAnyoneWon();
break;
case "2":
if (hint == 1)
{
string userChoice = GetString("Are you sure for use hint [PRESS Y]? (if there is no ship in row, One of your ship will sink)");
if (userChoice == "y")
{
bool result = ships.Hint(_enemyIsland, _enemyIslandCover);
hint = 0;
if (!result)
{
for (int i = 0; i < 3; i++)
{
int index = _myIsland.LastIndexOf('@');
_myIsland.RemoveAt(index);
_myIsland.Insert(index, '░');
}
DrawIslands();
HasAnyoneWon();
}
}
else
{
break;
}
}
else
{
GetError("You have no any hint.");
}
break;
case "3":
Console.Clear();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Thank you for choose my game. See you later");
Console.ForegroundColor = ConsoleColor.White;
Environment.Exit(0);
break;
default:
GetError("You Put Wrong Value!");
break;
}
}
private void FirstTimeGenerate()
{
for (int i = 0; i < 100; i++)
{
_myIsland.Add('■');
_enemyIsland.Add('■');
_enemyIslandCover.Add('·');
}
ships.GenerateShips(_myIsland);
ships.GenerateShips(_enemyIsland);
isGenerate = true;
}
private void Move(Direction dir)
{
_enemyIslandCover[cubePosition] = saveCharacter;
cubePosition += (int)dir;
if (cubePosition > 0 && cubePosition < 100)
{
saveCharacter = _enemyIslandCover[cubePosition];
_enemyIslandCover[cubePosition] = '■';
DrawIslands();
}
else
{
GetError("You Can't Move Outer the Field");
cubePosition = 0;
}
}
private void HasAnyoneWon()
{
if (ships.EnemyShip == 0 || ships.YourShip == 0)
{
int Counter = 0;
int HowManyTime = 0;
Console.Clear();
while (true)
{
HowManyTime += 10;
while (Counter < HowManyTime)
{
Console.Write("{0} ", _myIsland[Counter]);
Counter++;
}
Console.Write("║ ");
Counter -= 10;
while (Counter < HowManyTime)
{
Console.Write("{0} ", _enemyIsland[Counter]);
Counter++;
}
Console.WriteLine();
if (HowManyTime == 100)
{
break;
}
}
if (ships.EnemyShip == 0)
{
GetSucces("YOU WON ADMIRAL! WE BACK TO THE COUNTRY AND TELL TO PEOPLE ABOUT YOUR BRAVE!!!! HOOOOOOOOOORAY");
}
else
{
GetError("YOU LOOSE ADMIRAL! DON'T BE UPSET. PLAY GAME AGAIN!");
}
Environment.Exit(0);
}
}
// STATIC METHOD - ONCE WRITE EVERYWHERE USE
public static string GetString(string message)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write(message.ToLower());
Console.ForegroundColor = ConsoleColor.White;
return Console.ReadLine();
}
public static void GetError(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(message);
Console.ForegroundColor = ConsoleColor.White;
Thread.Sleep(3000);
}
public static void GetSucces(string message)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(message);
Console.ForegroundColor = ConsoleColor.White;
Thread.Sleep(2000);
}
}
And my last class named Ships because this class generate and create ships and all of things about ships :
class Ships
{
public int YourShip { get; set; }
public int EnemyShip { get; set; }
int indexOfAI = 0;
private GameEngine gameEnigne;
public Ships(GameEngine gameEngine, int howManyShip)
{
this.gameEnigne = gameEngine;
YourShip = howManyShip;
EnemyShip = howManyShip;
}
public void GenerateShips(List<char> island)
{
int counter = 0;
Random randomGenerator = new Random();
while (true)
{
if (counter == YourShip)
{
break;
}
int index = randomGenerator.Next(0, island.Count);
if (FindBestPlace(index, island))
{
island[index] = '@';
island[index - 1] = '@';
island[index + 1] = '@';
counter++;
}
else
{
continue;
}
}
}
private bool FindBestPlace(int index, List<char> island)
{
try
{
int Counter = index - (index % 10);
for (int i = Counter; i < Counter + 10; i++)
{
if (island[i] != '■')
{
return false;
}
}
if (index % 10 > 2 && index % 10 < 9)
{
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
public bool Hint(List<char> enemyIsland, List<char> enemyIslandCover)
{
Random rnd = new Random();
int index = 0;
bool sunkenShip = false;
bool thereIsAnyShip = false;
while (!sunkenShip)
{
index = enemyIsland.Count / 10 * (rnd.Next(0, 10));
sunkenShip = true;
thereIsAnyShip = false;
for (int i = index; i < index + 10; i++)
{
if (enemyIsland[i] == '░')
{
sunkenShip = false;
break;
}
if (enemyIsland[i] == '@')
{
thereIsAnyShip = true;
}
}
}
for (int i = index; i < index + 10; i++)
{
enemyIslandCover[i] = enemyIsland[i];
}
gameEnigne.DrawIslands();
Thread.Sleep(1000);
for (int i = index; i < index + 10; i++)
{
enemyIslandCover[i] = '·';
}
gameEnigne.DrawIslands();
if (!thereIsAnyShip)
{
YourShip--;
}
return thereIsAnyShip;
}
public bool Attack(List<char> enemyIsland, List<char> enemyIslandCover, int cubePosition)
{
if (enemyIsland[cubePosition] == '@')
{
int rowIndex = cubePosition - (cubePosition % 10);
for (int i = rowIndex; i < rowIndex + 10; i++)
{
if (enemyIsland[i] == '@')
{
enemyIsland[i] = '░';
enemyIslandCover[i] = '░';
}
}
gameEnigne.DrawIslands();
EnemyShip--;
return true;
}
else
{
enemyIslandCover[cubePosition] = '%';
return false;
}
}
public void EnemyAttack(List<char> MyIsland)
{
GameEngine.GetError("It's Turn of enemy | HIDE HIDE HIIIIIIIDE !!!! ");
int index;
Random rnd = new Random();
if (indexOfAI > 0)
{
if (indexOfAI < 70)
{
index = rnd.Next(indexOfAI, indexOfAI + 30);
}
else
{
index = rnd.Next(indexOfAI, MyIsland.Count);
}
}
else
{
index = rnd.Next(0, MyIsland.Count);
}
char saveChar = MyIsland[index];
MyIsland[index] = 'Ö';
gameEnigne.DrawIslands();
GameEngine.GetError("Enemy Choose His Location!! DON'T MOVE!! Sheeeeesh");
MyIsland[index] = saveChar;
bool isThereAnyShip = false;
if (MyIsland[index] == '@')
{
int Counter = index - (index % 10);
for (int i = Counter; i < Counter + 10; i++)
{
if (MyIsland[i] == '@')
{
MyIsland[i] = '░';
isThereAnyShip = true;
}
}
}
gameEnigne.DrawIslands();
if (isThereAnyShip)
{
YourShip--;
GameEngine.GetError("Enemy Sink One Of Your Ship Admiral, What's The Next Order ?");
indexOfAI = 0;
}
else
{
GameEngine.GetSucces("The danger was eliminated. The enemy does not know the location of our ship!!");
if (indexOfAI == 0)
{
SimpleEnemyAI(index, MyIsland);
}
else
{
indexOfAI = 0;
}
}
}
private void SimpleEnemyAI(int index , List<char> Island)
{
// it's a algorithm for Simple AI
if (index > 10)
{
index = index - (index % 10) - 10;
}
bool isAnyShipFind = false;
if (index < 70)
{
for (int i = index; i < index + 20; i++)
{
if (Island[i] == '@')
{
indexOfAI = index;
isAnyShipFind = true;
break;
}
}
}
else
{
for (int i = index; i < Island.Count; i++)
{
if (Island[i] == '@')
{
indexOfAI = index;
isAnyShipFind = true;
break;
}
}
}
if (isAnyShipFind)
{
GameEngine.GetError("But Careful! Enemy Have Radar And He Saw Vague Things In It!!");
}
else
{
indexOfAI = 0;
}
}
}
Explain:
first i create a class and named it GameEngine and Create a object from Ships Class and leave it NULL and create a constructor for it:
private Ships ships;
public GameEngine()
{
ships = new Ships(this, 5);
hint = 1;
}
i refrenced ships to a new Ship and send to it this Class as a object (i send exactly this game engine refrence and didn't create a new GameEngine because if i create a new GameEngine object then i can't access to the filled lists)
with this way ( thanks again from @aepot) i can access to the Ships Member And send a GameEngine object to the Ships Class and magicly i can access to the GameEngine Members in the ships class because i send a object from GameEngine to the Ships class. then i send 5 instead dynamic parameter to the Ships Constructor. That number is about the number of enemy ships and ourselves ( I could have made it dynamic, but I focused more on the game so i send a const number like 5 ) see:
public int YourShip { get; set; }
public int EnemyShip { get; set; }
int indexOfAI = 0;
private GameEngine gameEnigne;
public Ships(GameEngine gameEngine, int howManyShip)
{
this.gameEnigne = gameEngine;
YourShip = howManyShip;
EnemyShip = howManyShip;
}
i explain about indexOfAI.
i made three list. one of them is my Island or sea. One of them is enemy's Island or sea. And the last one is enemy's island but i covered it by dot because we haven't any permission to see the Enemy's island with details.
List<char> _myIsland = new List<char>();
List<char> _enemyIsland = new List<char>();
List<char> _enemyIslandCover = new List<char>();
then i write a FirstTimeGenerate Method And Call it in to the Engine Method:
private void FirstTimeGenerate()
{
for (int i = 0; i < 100; i++)
{
_myIsland.Add('■');
_enemyIsland.Add('■');
_enemyIslandCover.Add('·');
}
ships.GenerateShips(_myIsland);
ships.GenerateShips(_enemyIsland);
isGenerate = true;
}
in the FirstTimeGenerate you see GenerateShips Method.This method and FindBestPlace put ships in the island or sea:
public void GenerateShips(List<char> island)
{
int counter = 0;
Random randomGenerator = new Random();
while (true)
{
if (counter == YourShip)
{
break;
}
int index = randomGenerator.Next(0, island.Count);
if (FindBestPlace(index, island))
{
island[index] = '@';
island[index - 1] = '@';
island[index + 1] = '@';
counter++;
}
else
{
continue;
}
}
}
FindBestPlaceMethod:
private bool FindBestPlace(int index, List<char> island)
{
try
{
int Counter = index - (index % 10);
for (int i = Counter; i < Counter + 10; i++)
{
if (island[i] != '■')
{
return false;
}
}
if (index % 10 > 2 && index % 10 < 9)
{
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
FindBestPlace Search in the island and generate random number if the number of row is empty ( there is no ship in row) then create a ship otherwise search again and generate number again.
after that i create method for Draw the island and show information like user Choice and Score etc.
public void DrawIslands()
{
int Counter = 0;
int HowManyTime = 0;
Console.Clear();
while (true)
{
HowManyTime += 10;
while (Counter < HowManyTime)
{
Console.Write("{0} ", _myIsland[Counter]);
Counter++;
}
Console.Write("║ ");
Counter -= 10;
while (Counter < HowManyTime)
{
Console.Write("{0} ", _enemyIslandCover[Counter]);
Counter++;
}
Console.WriteLine();
if (HowManyTime == 100)
{
break;
}
}
}
private void ShowInformation()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("░ YOUR SHIPS: {0} \t ENEMY's SHIP: {1} \t HINT: {2}",
ships.YourShip, ships.EnemyShip, hint);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("░ 1. ATTACK \t 2. HINT \t 3. EXIT");
Console.ForegroundColor = ConsoleColor.White;
}
In this algorithm, first 10 Steps from our own sea are displayed, then a distance and 10 Steps from the enemy's sea (note that in the enemy's sea we only see points because we have no right to access the location of the enemy ship)
then i wrote Hint Method but first look at engine of hint:
case "2":
if (hint == 1)
{
string userChoice = GetString("Are you sure for use hint [PRESS Y]? (if
there is no ship in row, One of your ship will sink)");
if (userChoice == "y")
{
bool result = ships.Hint(_enemyIsland, _enemyIslandCover);
hint = 0;
if (!result)
{
for (int i = 0; i < 3; i++)
{
int index = _myIsland.LastIndexOf('@');
_myIsland.RemoveAt(index);
_myIsland.Insert(index, '░');
}
DrawIslands();
HasAnyoneWon();
}
}
else
{
break;
}
}
else
{
GetError("You have no any hint.");
}
break;
Hint Method:
public bool Hint(List<char> enemyIsland, List<char> enemyIslandCover)
{
Random rnd = new Random();
int index = 0;
bool sunkenShip = false;
bool thereIsAnyShip = false;
while (!sunkenShip)
{
index = enemyIsland.Count / 10 * (rnd.Next(0, 10));
sunkenShip = true;
thereIsAnyShip = false;
for (int i = index; i < index + 10; i++)
{
if (enemyIsland[i] == '░')
{
sunkenShip = false;
break;
}
if (enemyIsland[i] == '@')
{
thereIsAnyShip = true;
}
}
}
for (int i = index; i < index + 10; i++)
{
enemyIslandCover[i] = enemyIsland[i];
}
gameEnigne.DrawIslands();
Thread.Sleep(1000);
for (int i = index; i < index + 10; i++)
{
enemyIslandCover[i] = '·';
}
gameEnigne.DrawIslands();
if (!thereIsAnyShip)
{
YourShip--;
}
return thereIsAnyShip;
}
if user Choose 2 Hint Method Run But first i put some expression for user: first of all check user have hint or not. then i ask a question are you sure for use your hint, because if you use it and don't find any ship you lose your ship. then i send List of Enemy island and list of cover. in Hint method, a random number will be create and check if the row of random number have a sink ship again search for the new row and create new random number.
then if you press 1 and if it's your turn you can attack
case "1":
cubePosition = 0;
saveCharacter = _enemyIslandCover[cubePosition];
_enemyIslandCover[cubePosition] = '■';
DrawIslands();
bool isEnter = false;
while (!isEnter)
{
ConsoleKeyInfo readKey = Console.ReadKey();
switch (readKey.Key)
{
case ConsoleKey.UpArrow:
Move(Direction.Up);
break;
case ConsoleKey.DownArrow:
Move(Direction.Down);
break;
case ConsoleKey.RightArrow:
Move(Direction.Right);
break;
case ConsoleKey.LeftArrow:
Move(Direction.Left);
break;
case ConsoleKey.Enter:
isEnter = true;
bool result = ships.Attack(_enemyIsland, _enemyIslandCover ,
cubePosition);
if (result)
{
GetSucces("You Sink One of the Enemy's Ship!! POWER");
if (hint == 0)
{
hint++;
}
HasAnyoneWon();
}
else
{
GetError("DAMN IT ADMIRAL!!!");
}
break;
}
}
ships.EnemyAttack(_myIsland);
HasAnyoneWon();
break;
at the first i put the cursor or point on the first of enemy's sea ( On Zero ). then you can move the curser real time with this method and enum:
Enum :
enum Direction
{
Up = -10,
Left = -1,
Right = 1,
Down = 10
}
Up = -10 means if you choose up cursor move -10 step back on the list and ...
Move Method:
private void Move(Direction dir)
{
_enemyIslandCover[cubePosition] = saveCharacter;
cubePosition += (int)dir;
if (cubePosition > 0 && cubePosition < 100)
{
saveCharacter = _enemyIslandCover[cubePosition];
_enemyIslandCover[cubePosition] = '■';
DrawIslands();
}
else
{
GetError("You Can't Move Outer the Field");
cubePosition = 0;
}
}
in this method i save the next house character that the square is going to occupy in saveCharacter variable, which I previously defined outside of this method and in the same class.
then if you press enter you can attack on the square house:
case ConsoleKey.Enter:
isEnter = true;
bool result = ships.Attack(_enemyIsland, _enemyIslandCover ,
cubePosition);
if (result)
{
GetSucces("You Sink One of the Enemy's Ship!! POWER");
if (hint == 0)
{
hint++;
}
HasAnyoneWon();
}
else
{
GetError("DAMN IT ADMIRAL!!!");
}
break;
i send lists of enemy and cube position ( index of cube ). in the attack method i wrote a algorithm. if i exactly shot on the ship then ship sink and Enemy lose an score otherwise nothing happend and just a sign displayed that is '%' and it's because you remember you shot this location
public bool Attack(List<char> enemyIsland, List<char> enemyIslandCover, int cubePosition)
{
if (enemyIsland[cubePosition] == '@')
{
int rowIndex = cubePosition - (cubePosition % 10);
for (int i = rowIndex; i < rowIndex + 10; i++)
{
if (enemyIsland[i] == '@')
{
enemyIsland[i] = '░';
enemyIslandCover[i] = '░';
}
}
gameEnigne.DrawIslands();
EnemyShip--;
return true;
}
else
{
enemyIslandCover[cubePosition] = '%';
return false;
}
}
this is exactly happend when enemy attack but with few diffrent. look:
public void EnemyAttack(List<char> MyIsland)
{
GameEngine.GetError("It's Turn of enemy | HIDE HIDE HIIIIIIIDE !!!! ");
int index;
Random rnd = new Random();
if (indexOfAI > 0)
{
if (indexOfAI < 70)
{
index = rnd.Next(indexOfAI, indexOfAI + 30);
}
else
{
index = rnd.Next(indexOfAI, MyIsland.Count);
}
}
else
{
index = rnd.Next(0, MyIsland.Count);
}
char saveChar = MyIsland[index];
MyIsland[index] = 'Ö';
gameEnigne.DrawIslands();
GameEngine.GetError("Enemy Choose His Location!! DON'T MOVE!! Sheeeeesh");
MyIsland[index] = saveChar;
bool isThereAnyShip = false;
if (MyIsland[index] == '@')
{
int Counter = index - (index % 10);
for (int i = Counter; i < Counter + 10; i++)
{
if (MyIsland[i] == '@')
{
MyIsland[i] = '░';
isThereAnyShip = true;
}
}
}
gameEnigne.DrawIslands();
if (isThereAnyShip)
{
YourShip--;
GameEngine.GetError("Enemy Sink One Of Your Ship Admiral, What's The Next Order
?");
indexOfAI = 0;
}
else
{
GameEngine.GetSucces("The danger was eliminated. The enemy does not know the
location of our ship!!");
if (indexOfAI == 0)
{
SimpleEnemyAI(index, MyIsland);
}
else
{
indexOfAI = 0;
}
}
}
enemy generate a random house of my island or sea and shot it if it's shot exactly on my ships then my score decreases but if he can't shot my ship then a method run named SimpleEnemyAI(). this method find near the shot and look at my ship. if there is ship in the area then set the enemy index closer.
SimpleEnemyAI Method:
private void SimpleEnemyAI(int index , List<char> Island)
{
// it's a algorithm for Simple AI
if (index > 10)
{
index = index - (index % 10) - 10;
}
bool isAnyShipFind = false;
if (index < 70)
{
for (int i = index; i < index + 20; i++)
{
if (Island[i] == '@')
{
indexOfAI = index;
isAnyShipFind = true;
break;
}
}
}
else
{
for (int i = index; i < Island.Count; i++)
{
if (Island[i] == '@')
{
indexOfAI = index;
isAnyShipFind = true;
break;
}
}
}
if (isAnyShipFind)
{
GameEngine.GetError("But Careful! Enemy Have Radar And He Saw Vague Things In
It!!");
}
else
{
indexOfAI = 0;
}
}
And in order for the game not to be difficult, if you pay attention to the method, you will realize that this does not happen every time.
This was my exercise. I would be very grateful if you could please remind me of my problems and if there is anything that will improve me.
Counter
) and the next you're camel casing (i.e.index
,indexOfAI
, etc). General standard is to have local variables and parameters be camelCased and have classes, member variables, etc be PascalCased. \$\endgroup\$