跳到主要内容

C# 基础入门

一、语言概述 💻

C#(发音为“C sharp”)是由微软公司设计开发的一种现代、类型安全的面向对象编程语言。它在保持 C++ 语言的表达力和高效性的同时,还提供了自动内存管理、异常处理等现代化特性。

信息

# 符号在音乐记谱法中表示升调(Sharp),当一个音符标记为 # 时,表示这个音要升高半音。这个命名展现了微软对这门语言的野心和定位——既要继承 C 语言家族的优良传统,又要在此基础上有所提升和突破。

C# 作为一种多功能且功能强大的语言,广泛应用于各个领域。特别在游戏编程中非常受欢迎,凭借其强大的性能和灵活性,已成为游戏开发者的首选语言。

提示

本教程将从游戏开发的实际需求出发,聚焦实用语法和技能。

C# 最为人所知的是用于在 Unity 游戏引擎中编写脚本的语言。 Godot 引擎也支持用 C# 编写游戏逻辑。

Unity 通过使用 Mono 实现跨平台 C# 脚本支持。

Godot 3.x 使用 Mono 作为 C# 运行时环境。Godot 4.x 采用原生 .NET 支持,逐步迁移到了现代 .NET 生态系统。

信息

Mono 是一个开源的 .NET 框架实现,提供跨平台的开发解决方案,为开发者提供完整的 .NET 兼容性环境。

二、语言特性 👥

  • 面向对象:C# 支持面向对象编程的原则,使得组织和结构化代码变得更容易。

  • 类型安全:C# 是一种静态类型语言,这意味着变量必须声明为特定的数据类型,从而增强代码安全性。

  • 内存管理:C# 通过垃圾回收实现自动内存管理,减少内存泄漏的风险。

  • 现代语言特性:C# 不断发展,引入了许多现代编程语言的特性,使得开发更加高效和灵活。

  • 开源生态系统:C# 拥有活跃的开源社区,提供了大量的开源项目、文档和资源,有助于解决跨平台开发中遇到的问题。

三、开发环境 🖥️

1、.NET SDK 🔧

.NET SDK:该软件开发套件包括编译器、库和其他资源用于用C#开发应用程序。

👉 .NET SDK 下载

2、.NET Framework 🔧

.NET Framework 是一个已经存在多年的传统框架,Windows平台专用。

3、.NET Core 🔧

.NET Core 是 .NET 的跨平台开源版本,旨在支持现代应用程序。

4、.NET 🔧

.NET 平台融合了 .NET Framework 和 .NET Core,并为开发人员提供一个单一的平台来创建任何类型的应用程序。这些发展是为了适应现代技术趋势以及提高性能和可扩展性的需求。

5、开发工具 📊

C# 的主要 IDE 是 Microsoft 的 Visual Studio。

不过,还有其他选择,例如 Visual Studio Code(支持 C# 扩展的轻量级代码编辑器)和 JetBrains Rider。

每个环境都有自己的优点和特点,选择取决于开发人员的具体需求:

  • Visual Studio:全面的 IDE,具有适用于大型项目和多语言支持。

  • JetBrains Rider:跨平台 .NET IDE,具有强大的 .NET 开发工具和丰富的插件。

四、Hello World 👋

打开开发工具并创建一个控制台项目或者使用命令行 dotnet new console 创建一个新的控制台应用,dotnet run 编译并运行一个简单的 C# 程序。

public class Program
{
public static void Main()
{
Console.WriteLine("Hello World!");
}
}

C# 程序中的入口点通常由位于 Program 类中的 Main() 方法表示。该方法必须是静态的,并作为程序执行的起点。

void是一个关键字,意味着我们程序的这一部分(称为方法)不会“返回”任何值。

Console.WriteLine()Console.Write() 都将信息打印到控制台。

通用的C#程序结构:

public class Program
{
public static void Main(string[] args)
{
// program code
}
}

args 参数包含一个字符串数组,该数组在程序启动时传递给程序。

五、变量和数据类型 👀

变量用于在程序中存储数据。

1、变量命名规则 📚

变量名称应该是描述性的。

  • 变量名称必须以字母或下划线开头。当您想以数字开头变量名(例如 6thEnemy2DBackground)时,但在 C# 中是不允许的。相反,请使用 enemy6background2D

  • 在第一个字母或下划线之后,我建议主要使用字母、数字或下划线,除非您有充分的理由使用其他内容。可以使用一些不常见的字符,但其他字符会导致语法错误。

  • 变量名中不能使用空格。空格实际上将变量名分成两个变量名,并且会使编译器感到困惑。例如,playerScore 是一个变量,而 player Score 将被视为两个变量。

  • 当变量名包含多个单词时,请使用 驼峰式大小写

提示

像这样编写多单词变量名称playerfirstName 是可以的,但这些单词往往会混合在一起。为了使名称中的特定单词更清晰,请使用 驼峰式大小写,这意味着第一个单词后面的每个单词都使用大写字母,例如playerFirstName。您可以将第一个字母大写,但根据 C# 约定,变量名称以小写第一个字母开头。

2、基本数据类型 📊

  • 整数类型:int(整型)、long(长整型)、short(短整型)、byte(字节型)

  • 浮点类型:float(单精度浮点型)、double(双精度浮点型)、decimal(十进制浮点型)

  • 字符类型:char

  • 布尔类型:bool

  • 字符串类型:string

我们需要指定变量类型的原因之一是让 C# 知道要留出多少内存。

int 存储在 32 位或 4 个字节的内存中。

如果您需要节省内存并且只需要存储小范围内的数字,那么使用 byteshort 是正确的选择。

float 是我们存储小数的首选。与 int 一样存储在 32 位或 4 字节内存中。

double 使用 float 所需内存的两倍(因此得名 double),但更精确(更多小数位),而 decimal 是内存大小是 float 的四倍,并提供更高的精度(甚至更多的小数位)!

char 存储在 16 位或 2 个字节的内存中。

bool 存储在 1 位内存中,但在大多数系统中通常分配 1 个字节(8 位)以提高效率。

string 是我们用来存储文本的类型。字符串实际上是由字符组成的,因此它们使用的内存量取决于字符串的长度。

public class Program
{
public static void Main()
{
Console.WriteLine(10);
Console.WriteLine(3.14159);
Console.WriteLine(-0.5f);
Console.WriteLine('Y');
Console.WriteLine(true);
Console.WriteLine("games");
}
}
数据类型内存大小说明
bool1 位(通常分配 1 字节)存储逻辑值 true 或 false
byte8 位(1 字节)存储小范围整数
short16 位(2 字节)存储较小范围的整数
int32 位(4 字节)存储标准整数
long64 位(8 字节)存储非常大的整数
char16 位(2 字节)存储单个字符
float32 位(4 字节)存储单精度浮点数
double64 位(8 字节)存储双精度浮点数,比 float 更精确
decimal128 位(16 字节)存储高精度十进制数
string可变(取决于长度)存储文本,由字符组成

3、声明变量 💡

C#中,您通过指定数据类型和名称来声明变量。

必须先声明变量,然后才能对其进行赋值。

要创建变量,我们必须声明它 - 为此,我们需要告诉 C# 变量的类型(它将存储什么样的值)并为其命名(程序员定义的标签,我们将使用它来访问变量)。

一般来说,声明语句如下所示:type variableName; (type 是数据类型,variableName 是变量名称)

变量声明仅创建变量,它不存储值。

第一种格式(声明多个变量)

声明多个同类型变量时更简洁。

int playerScore, playerHealth, gameHighScore;
float scoreModifier;
string playerFirstName, playerLastName;
bool playerHasHighScore, gameOver;
第二种格式(每个变量单独声明)

可读性上更为突出。

int playerScore;
int playerHealth;
int gameHighScore;
float scoreModifier;
string playerFirstName;
string playerLastName;
bool playerHasHighScore;
bool gameOver;

4、变量赋值 📝

赋值语句如下所示:variableName = someValue; (variableName 是变量名称,someValue 是值)

5、初始化变量 🔄

初始化=声明+赋值

public class Program
{
public static void Main()
{
// Declaration without initialization
int playerHealth;
// Initialization later in the program
playerHealth = 100;
Console.WriteLine("Player's Health is " + playerHealth);
// Initialization at declaration
float distanceToTarget = 10.9f;
Console.WriteLine("Distance to target: " + distanceToTarget);
}
}

6、类型推断 🔗

C# 支持使用 var 关键字进行类型推断,允许编译器根据赋值自动确定数据类型。

public class Program
{
public static void Main()
{
// Compiler infers string
var name = "Player";
// Compiler infers float
var score = 100.5f;

Console.WriteLine($"Name: {name}");
Console.WriteLine($"Score: {score}");
}
}

7、常量 🔒

常量是指一旦赋值就不能更改其值的变量。它们使用 const 关键字声明:

const double PI = 3.14159;

当需要定义一个在程序生命周期中不会改变的变量时,应该使用 const 变量。

常量的关键特征:

  • 必须在声明时就初始化

  • 值在编译时就必须确定

C# 中,常量的命名约定通常遵循 Pascal 大小写(每个单词首字母大写)的规则,并且全部使用大写字母,单词之间用下划线分隔。这被称为“大写蛇形命名法”(UPPER_SNAKE_CASE)。

public class GameConstants
{
public const int MAX_PLAYER_LEVEL = 100;
public const int INITIAL_HEALTH_POINTS = 100;
public const string GAME_VERSION = "1.0.0";
}

8、可空类型 🤔

C# 中的可为空类型允许表示值类型不存在或未初始化的值。

C# 中,值类型不能被赋值为null。然而,通过使用可空类型,您可以明确允许值类型为null。

int? nullableInt = null;

要检查值是否存在,可以使用 HasValue 属性,要检索值本身,可以使用 Value

?? 运算符是一个空合并运算符,如果左操作数不为空,则返回该操作数;否则,它返回右操作数。

public class Program
{
public static void Main()
{
int? number = null;
number = 6;

if (number.HasValue)
{
Console.WriteLine(number.Value);
}

int? a = null;
int? b = 10;
int c = a ?? b ?? 0;
Console.WriteLine(c);
}
}

9、类型转换 🔀

隐式类型转换

隐式类型转换会自动发生,当可以容纳较少信息的数据类型转换为可以容纳更多信息的数据类型(例如,从 intdouble)时。

int playerHealth = 100;       // int
float healthPercentage = playerHealth; // int → float
显式转换

当转换过程中存在数据丢失的风险时,需要显式类型转换(强制转换)。

float enemyDamage = 50.5f;
int roundedDamage = (int)enemyDamage; // float → int
值类型转换方法

1、Convert 类

string scoreText = "200";
int playerScore = Convert.ToInt32(scoreText);

2、Parse 方法

专注于字符串转换,性能更好,但规则更严格,配合 TryParse 使用,实现安全的转换。

string dateStr = "2008-10-31";
if (DateTime.TryParse(dateStr, out DateTime gameDate))
{
Console.WriteLine(gameDate);
}
else
{
Console.WriteLine("Invalid date format.");
}
安全转换

as 用于引用类型的安全类型转换,如果无法转换则返回 null,而不是抛出异常。is 用于检查对象是否为特定类型。

public class GameCharacter
{
public string Name { get; set; }
}

public class Player : GameCharacter
{
public int Score { get; set; }
}

public class Enemy : GameCharacter
{
public int Health { get; set; }
}

public class GameManager
{
public void ProcessCharacter(object character)
{
if (character is Player player)
{
Console.WriteLine($"Player {player.Name}, Score: {player.Score}");
}
else if (character is Enemy enemy)
{
Console.WriteLine($"Enemy {enemy.Name}, Health: {enemy.Health}");
}

var gameChar = character as GameCharacter;
if (gameChar != null)
{
Console.WriteLine($"Character Name: {gameChar.Name}");
}
}
}

public class Program
{
public static void Main()
{
var gameManager = new GameManager();
object[] characters = {
new Player { Name = "Hero", Score = 100 },
new Enemy { Name = "Joker", Health = 50 },
};

foreach (var character in characters)
{
gameManager.ProcessCharacter(character);
}
}
}

六、字符串 🔝

在游戏开发中,字符串处理非常常用,例如,文本本地化、UI文本显示、配置解析、数据存储等等。

字符串是字符序列。 在 C# 中,字符串是 Unicode 字符序列。 它是一种数据类型,用于存储一系列数据值(通常为字节)。

1、声明和初始化 📝

C# 中的字符串类型是不可变的,这意味着每次修改字符串时,都会创建一个新实例。

string greeting = "Hello, C#!";
string emptyName = string.Empty;

2、常用操作 🔄

字符串拼接

// Use the + operator to concatenate multiple strings and variables together
string firstName = "Satoshi";
string lastName = "Nakamoto";
string fullName = firstName + " " + lastName;

字符串插值

string firstName = "Satoshi";
string lastName = "Nakamoto";
string message = $"Hello, {firstName} {lastName}!";

逐字字符串

string normalPath = "C:\\Program Files\\dotnet";
// No need to escape backslashes
string verbatimPath = @"C:\Program Files\dotnet";

常用字符串方法

string text = "   C# Programming   ";
string trimmedText = text.Trim();
string leftTrim = text.TrimStart();
string rightTrim = text.TrimEnd();
string upperCaseText = text.ToUpper();
string lowerCaseText = text.ToLower();
int length = text.Length;
char firstChar = text[0];

查找和替换

string text = "Hello World";

bool contains = text.Contains("World"); // Contains check
int index = text.IndexOf("o"); // Find position
int lastIndex = text.LastIndexOf("o"); // Last occurrence position

string newText = text.Replace("World", "C#"); // Replace string
string noSpaces = text.Replace(" ", ""); // Remove spaces

字符串比较

string str1 = "hello";
string str2 = "HELLO";

// Comparison methods
bool isEqual1 = str1 == str2; // Case-sensitive
bool isEqual2 = str1.Equals(str2, StringComparison.OrdinalIgnoreCase); // Case-insensitive

// Compare sizes
int result = string.Compare(str1, str2); // Returns -1, 0, or 1

分割和连接

// Splitting game skills
string skillConfig = "Fireball IceSword LightningStrike";
string[] skills = skillConfig.Split(' ');

// Joining game items
string[] items = { "Sword", "Shield", "Potion" };
string playerInventory = string.Join(" + ", items);

格式化

// Basic formatting
string playerName = "Hero";
int level = 99;
string status = string.Format("Player {0} reached level {1}", playerName, level);

// Interpolated string
string gameInfo = $"Player {playerName} current level {level}";

// Number formatting
double damage = 123.456;
string damageText = $"Damage: {damage:F2}"; // Keep two decimal places

// Currency formatting
double gold = 1234.56;
string goldText = $"Gold: {gold:C}"; // Currency format

// Alignment formatting
string itemLog = $"{'Item Name',10}{'Quantity',5}";

// Base conversion
int exp = 100;
string hexExp = $"Experience: {exp:X}"; // Hexadecimal

3、StringBuilder

StringBuilder 旨在高效地修改字符串,而无需创建大量新实例。

using System.Text;

class GameLogBuilder
{
public string BuildGameLog()
{
// Create StringBuilder
StringBuilder log = new StringBuilder();

// Append log information
log.Append("Player entered the game ");
log.AppendLine("Time: " + DateTime.Now);

// Add multiple pieces of information
log.AppendFormat("Level: {0} ", 99);
log.AppendLine("Map: Main City");

// Insert information
log.Insert(0, "[Game Log] ");

// Replace information
log.Replace("Main City", "Novice Village");

// Get the final log
string finalLog = log.ToString();
return finalLog;
}
}

4、正则表达式 🔗

正则表达式基础

using System;
using System.Text.RegularExpressions;

class RegexDemo
{
static void Main()
{
// Create Regex objects
Regex digitRegex = new Regex(@"\d+"); // Matches one or more digits
Regex emailRegex = new Regex(@"\b\w+@\w+\.\w+\b"); // Simple email match

// Directly match digits
bool hasDigits = Regex.IsMatch("Player123", @"\d+");
Console.WriteLine($"Contains digits: {hasDigits}");

// Email validation
string email = "player@game.com";
bool isValidEmail = Regex.IsMatch(email, @"\b\w+@\w+\.\w+\b");
Console.WriteLine($"Is email valid: {isValidEmail}");

// Extract matched content
string text = "Player Level: 99, Score: 1000";
Match match = Regex.Match(text, @"\d+");
if (match.Success)
{
Console.WriteLine($"Extracted number: {match.Value}");
}
}
}

常用匹配模式

public class RegexPatterns
{
// Number matching
public static readonly string Numbers = @"\d+";

// Email matching
public static readonly string Email = @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$";

// Phone number (China)
public static readonly string ChinesePhone = @"^1[3-9]\d{9}$";

// URL matching
public static readonly string Url = @"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)";

// Date format (yyyy-MM-dd)
public static readonly string Date = @"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$";
}

正则表达式组

using System;
using System.Text.RegularExpressions;

class RegexGroupDemo
{
static void Main()
{
// Match player information: Name (group) and Level (group)
string pattern = @"(\w+):(\d+)";
string input = "Hero:99 Mage:80 Archer:75";

// Find all matches
MatchCollection matches = Regex.Matches(input, pattern);

// Iterate through groups
foreach (Match match in matches)
{
// Entire match
Console.WriteLine($"Full match: {match.Value}");

// Group access
string playerName = match.Groups[1].Value;
string playerLevel = match.Groups[2].Value;

Console.WriteLine($"Player: {playerName}, Level: {playerLevel}");
}

// Named groups
string namedPattern = @"(?<Name>\w+):(?<Level>\d+)";
Match namedMatch = Regex.Match(input, namedPattern);

if (namedMatch.Success)
{
Console.WriteLine($"Named group - Player: {namedMatch.Groups["Name"].Value}");
Console.WriteLine($"Named group - Level: {namedMatch.Groups["Level"].Value}");
}
}
}

C#开发中,处理字符串和正则表达式是一个不可或缺的部分。无论是操作字符串、执行模式匹配,还是利用正则表达式进行复杂的文本处理,掌握这些概念可以增强您在C#中有效处理和处理文本数据的能力。

七、运算符 🔢

1、算术运算符 🧮

算术运算符+-*/% 用于执行算术运算。

它们包括:

  • 加法 (+): 将两个操作数相加。
  • 减法 (-): 从左操作数中减去右操作数。
  • 乘法 (*): 将两个操作数相乘。
  • 除法 (/): 将左操作数除以右操作数。
  • 取模 (%): 返回除法的余数。

2、比较运算符 🔃

比较运算符==!=<><=>= 用于比较值。

它们包括:

  • 等于 (==): 检查两个操作数是否相等。
  • 不等于 (!=): 检查两个操作数是否不相等。
  • 大于 (>): 检查左操作数是否大于右操作数。
  • 小于 (<): 检查左操作数是否小于右操作数。
  • 大于或等于 (>=): 检查左操作数是否大于或等于右操作数。
  • 小于或等于 (<=): 检查左操作数是否小于或等于右操作数。
提示

比较(==!=)和关系(<><=>=)运算符用于比较两个值。
重要的是要记住,在比较引用类型时,== 运算符检查引用是否相等,而不是内容。

3、逻辑运算符 🔄

逻辑运算符&&||! 用于创建逻辑表达式。

它们包括:

  • 逻辑与 (&&): 如果两个操作数都为真,则返回真。
  • 逻辑或 (||): 如果至少一个操作数为真,则返回真。
  • 逻辑非 (!): 如果操作数为假,则返回真,反之亦然。

4、赋值运算符 🔧

赋值运算符用于给变量赋值。

赋值 (=):将右侧的值赋给左侧的变量。

5、位运算符 🔃

位运算符&|^~<<>> 用于对位进行操作。

位运算允许在数值的各个位级别进行操作。

6、优先级 🔢

了解运算符优先级很重要,因为它会影响运算顺序。

提示

优先级从高到低排序。

类别运算符结合性
主要x.y, f(x), a[x], x++, x--左到右
一元+, -, !, ~, ++x, --x, (T)x右到左
乘除*, /, %左到右
加减+, -左到右
移位<<, >>左到右
关系<, >, <=, >=, is, as左到右
相等==, !=左到右
逻辑与&左到右
逻辑异或^左到右
逻辑或|左到右
条件与&&左到右
条件或||左到右
条件?:右到左
赋值=, +=, -=, *=, /=右到左

7、运算符重载 🔢

C# 中,运算符重载允许您重新定义内置运算符对用户定义类型(例如类和结构)的工作方式。

要重载运算符,请在类或结构中定义静态方法,并使用运算符关键字后跟要重载的运算符符号。

该方法必须返回一个结果,并至少采用一个您要为其重载运算符的类型的参数。

下面是一个为自定义 Vector 类重载 + 运算符的简单示例:

public class Program
{
public static void Main()
{
Vector vector1 = new Vector(1, 2);
Vector vector2 = new Vector(2, 3);
Vector result = vector1 + vector2; // This will call the overloaded + operator
Console.WriteLine(result);
}
public class Vector(int x, int y)
{
private int X { get; } = x;
private int Y { get; } = y;

// Overload + operator
public static Vector operator +(Vector v1, Vector v2)
{
return new Vector(v1.X + v2.X, v1.Y + v2.Y);
}

public override string ToString()
{
return $"Vector({X}, {Y})";
}
}
}

八、流程控制 🔄

控制结构和循环是任何程序的基本元素,使开发人员能够有效地管理代码执行流程。

1、条件语句 🤔

  • if-else:用于根据条件的真实性执行操作
  • switch-case:用于从多种可能性中选择一个代码块来执行

if-else 语句用于条件执行。它们允许您的程序根据特定条件是 true 还是 false 来采用不同的路径。

switch 运算符允许您根据多个值检查变量。

它更紧凑,通常更方便检查单个变量的值。

嵌套控制结构:您可以将控制结构相互嵌套以创建更复杂的逻辑。

public class Program
{
public static void Main()
{
CheckPlayerStatus(99, true);
}

static void CheckPlayerStatus(int level, bool isVip)
{
// Basic if-else
if (level < 10)
{
Console.WriteLine("Newbie Player");
}
else if (level < 50)
{
Console.WriteLine("Regular Player");
}
else
{
Console.WriteLine("Advanced Player");
}

// Nested if-else
if (level >= 50)
{
if (isVip)
{
Console.WriteLine("Prestigious VIP Player");
}
else
{
Console.WriteLine("Regular Advanced Player");
}
}

// Complex condition check
if (level > 90 && isVip)
{
Console.WriteLine("Top VIP Player");
}

var playerType = level switch
{
< 10 => "Newbie",
>= 10 and < 50 => isVip ? "Regular VIP" : "Regular",
>= 50 and < 90 => isVip ? "Expert VIP" : "Expert",
>= 90 => isVip ? "Top VIP" : "Master"
};
Console.WriteLine($"Player Type: {playerType}");
}
}

2、循环结构 🔁

循环允许您重复执行一段代码。

  • for:当迭代次数已知时使用
  • foreach:用于迭代集合的所有元素
  • while:当迭代次数未知时使用
  • do-while:保证代码块至少执行一次

选择最佳循环取决于具体情况。

  • break 提前退出循环
  • continue 不会终止循环,而是继续执行下一次迭代

嵌套循环是放置在另一个循环内的循环。它通常用于处理二维数组或矩阵。

class SimpleRPGGame
{
// Player class
class Player(string? name)
{
private string? Name { get; } = name;
public int Health { get; set; } = 100;
public int Attack => 10;

public void DisplayStatus()
{
Console.WriteLine($"Player {Name} - Health: {Health}, Attack: {Attack}");
}
}

// Monster class
class Monster
{
public string Name { get; }
public int Health { get; set; }
public int Attack { get; }

public Monster()
{
string[] monsterNames = ["Goblin", "Skeleton Warrior", "Dragon"];
Random random = new Random();
Name = monsterNames[random.Next(monsterNames.Length)];
Health = random.Next(50, 100);
Attack = random.Next(5, 15);
}

public void DisplayStatus()
{
Console.WriteLine($"Monster {Name} - Health: {Health}, Attack: {Attack}");
}
}

// Main game logic
static void Main()
{
Console.WriteLine("Welcome to the Simple RPG Game!");

// Create player
Console.Write("Please enter your character name: ");
string? playerName = Console.ReadLine();
if (playerName != null)
{
Player player = new Player(playerName);

// Battle system
while (true)
{
// Generate random monster
Monster monster = new Monster();

Console.WriteLine("\nEncountered a new monster!");
monster.DisplayStatus();
player.DisplayStatus();

// Battle choices
Console.WriteLine("\nChoose your action:");
Console.WriteLine("1. Attack");
Console.WriteLine("2. Run away");
string? choice = Console.ReadLine();

if (choice == "1")
{
// Battle logic
while (player.Health > 0 && monster.Health > 0)
{
// Player attacks
monster.Health -= player.Attack;
Console.WriteLine($"You attacked {monster.Name}");

// Monster attacks
if (monster.Health > 0)
{
player.Health -= monster.Attack;
Console.WriteLine($"{monster.Name} attacked you");
}

// Display status
player.DisplayStatus();
monster.DisplayStatus();

// Check battle result
if (monster.Health <= 0)
{
Console.WriteLine("You defeated the monster!");
break;
}

if (player.Health <= 0)
{
Console.WriteLine("Game over, you were defeated!");
return;
}
}
}
else
{
Console.WriteLine("You chose to run away...");
continue;
}

// Ask if continue
Console.WriteLine("\nDo you want to continue the adventure? (y/n)");
if (Console.ReadLine()?.ToLower() != "y")
{
break;
}
}
}

Console.WriteLine("Game over, thank you for playing!");
}
}