Core message
Programshould not ask for internals and draw by itself.Programshould tell the object:sprite.MoveNDraw();
Stage 1 - First Pass (GS Snippet Style)
First pass is intentionally plain:
- all
privatefields first - constructor second
- vanilla Java-style getters/setters third
public class Sprite
{
private double x, y; // later make gs private setters with if
// just fields:
private double dX,dY; // just fields
private string shape; // just field
private ConsoleColor color; // just field
private int maxX, maxY; // just field
public Sprite(double x, double y, string shape, ConsoleColor color, double dx = 0.2, double dy = 0.2)
{
this.shape = shape;
this.color = color;
this.maxX = Console.WindowWidth - 1;
this.maxY = Console.WindowHeight - 1;
dX = dx;
dY = dy;
SetX(x);
SetY(y);
}
public double GetY() => y;
public void SetY(double value) => y = value;
public double GetX() => x;
public void SetX(double value) => x = value;
}
Bad Usage Example (Program asks for internals and draws by itself):
Sprite s = new Sprite(10, 5, "O", ConsoleColor.Green, 0.2, 0.2);
Console.ForegroundColor = ConsoleColor.Green;
Console.SetCursorPosition((int)Math.Round(s.GetX()), (int)Math.Round(s.GetY()));
Console.Write("O");
Teacher line:
- This works, but
Programis doing the sprite’s drawing job.
Stage 2 - Add Draw/Erase as One Method
Now give the object its own console behavior.
+private int GetXInt() => (int)Math.Round(x);
+private int GetYInt() => (int)Math.Round(y);
+
+/// <summary>
+/// Draws in the given color.
+/// Call without color to erase (default black).
+/// </summary>
+public void DrawErase(ConsoleColor drawColor = ConsoleColor.Black)
+{
+ Console.ForegroundColor = drawColor;
+ Console.SetCursorPosition(GetXInt(), GetYInt());
+ Console.Write(shape);
+}
How to use right now:
+Sprite s = new Sprite(10, 5, "O", ConsoleColor.Green,0.2,0.2);
+s.DrawErase(ConsoleColor.Green);
Teacher line:
- Default argument is black, so same method can erase.
Stage 3 - Add MoveNDraw (Dont Ask Tell)
Main behavior: erase old, move by dX/dY, draw new.
- public double GetY() => y; // ==BREAKING Change==
- public double GetX() => x; // ==BREAKING Change==
+
+public void MoveNDraw()
+{
+ DrawErase();
+ SetX(x + dX);
+ SetY(y + dY);
+ DrawErase(color);
+}
Teacher line:
- Program no longer calculates cursor location or paint details.
- Program tells sprite to perform its own job.
- Old
Programcode that relied onGetX/GetYshould now break, on purpose.
Stage 4 - Modify the Vanilla Code for Game Rules (Bounds Protection)
Edit the generated constructor/getters/setters (do not rewrite from scratch).
At this stage, keep movement updates internal and add bounds protection in SetX/SetY.
-public void SetX(double value) => x = value;
+private void SetX(double value)
+ {
+ if (value < 0) x = 0;
+ else if (value > maxX) x = maxX;
+ else x = value;
+ }
-public void SetY(double value) => y = value;
+private void SetY(double value)
+{
+ if (value < 0) y = 0;
+ else if (value > maxY) y = maxY;
+ else y = value;
+}
Teacher line:
- We use
doublefor movement accuracy, but console draw needs rounded ints. Programcannot directly setx/yanymore; it must tell the sprite what to do.
Stage 5 - Program Loop
Very small demo loop for class.
+Sprite s1 = new Sprite(10, 5, "O", ConsoleColor.Yellow, 0.35, 0.18);
+Sprite s2 = new Sprite(10, 5, "O", ConsoleColor.Yellow, 0.35, -0.18);
+
+while (!Console.KeyAvailable)
+{
+ s1.MoveNDraw();
+ s2.MoveNDraw();
+ Thread.Sleep(30);
+}
Optional quick key to stop:
Console.ReadKey(true);
Later Stage (Optional): key constructor + while-only game loop
Use this in a later lesson so each sprite can own its control mapping and keyboard behavior. Default mapping is arrows from the base constructor; overload only when you want custom keys.
+private ConsoleKey leftKey;
+private ConsoleKey rightKey;
+private ConsoleKey upKey;
+private ConsoleKey downKey;
+
+public Sprite(double x, double y, string shape, ConsoleColor color)
+{
+ this.shape = shape;
+ this.color = color;
+ this.maxX = Console.WindowWidth - 1;
+ this.maxY = Console.WindowHeight - 1;
+ this.leftKey = ConsoleKey.LeftArrow;
+ this.rightKey = ConsoleKey.RightArrow;
+ this.upKey = ConsoleKey.UpArrow;
+ this.downKey = ConsoleKey.DownArrow;
+ SetX(x);
+ SetY(y);
+}
+
+public Sprite(double x, double y, string shape, ConsoleColor color, ConsoleKey leftKey, ConsoleKey rightKey, ConsoleKey upKey, ConsoleKey downKey)
+ : this(x, y, shape, color)
+{
+ this.leftKey = leftKey;
+ this.rightKey = rightKey;
+ this.upKey = upKey;
+ this.downKey = downKey;
+}
+
+public void HandleKey(ConsoleKey key)
+{
+ if (key == leftKey) SetDX(-Math.Abs(dX) - 0.1);
+ else if (key == rightKey) SetDX(Math.Abs(dX) + 0.1);
+ else if (key == upKey) SetDY(-Math.Abs(dY) - 0.1);
+ else if (key == downKey) SetDY(Math.Abs(dY) + 0.1);
+}
Note: This optional HandleKey snippet assumes SetDX/SetDY methods exist (shown in the final state below).
Teacher line:
- This still follows Dont Ask Tell:
sprite.HandleKey(key)instead of key logic spread inProgram.
While-only game loop template (no do-while):
bool running = true;
while (running)
{
while (!Console.KeyAvailable)
{
s.MoveNDraw();
Thread.Sleep(30);
}
ConsoleKey key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Escape)
running = false;
else
s.HandleKey(key);
}
Final Sprite.cs (Simple Version)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class Sprite
{
private double x, y; // gs private setters
//just fields
private double dX, dY; // just fields
private string shape; // just field
private ConsoleColor color; // just field
private int maxX, maxY; // just field
public Sprite(double x, double y, string shape, ConsoleColor color, double dx = 0.2, double dy = 0.2)
{
this.shape = shape;
this.color = color;
this.maxX = Console.WindowWidth - 1;
this.maxY = Console.WindowHeight - 1;
dX = dx;
dY = dy;
SetX(x);
SetY(y);
}
private void SetX(double value)
{
if (value < 0) x = 0;
else if (value > maxX) x = maxX;
else x = value;
}
private void SetY(double value)
{
if (value < 0)
y = 0;
else if (value > maxY) y = maxY;
else y = value;
}
private int GetXInt() => (int)Math.Round(x);
private int GetYInt() => (int)Math.Round(y);
/// <summary>
/// Draws in the given color.
/// Call without color to erase (default black).
/// </summary>
public void DrawErase(ConsoleColor drawColor = ConsoleColor.Black)
{
Console.ForegroundColor = drawColor;
Console.SetCursorPosition(GetXInt(), GetYInt());
Console.Write(shape);
}
public void MoveNDraw()
{
DrawErase();
SetX(x + dX);
SetY(y + dY);
DrawErase(color);
}
}
Final Program.cs (Simple Demo)
1
2
3
4
5
6
7
8
9
10
11
12
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.CursorVisible = false;
Sprite s = new Sprite(10, 5, "O", ConsoleColor.Yellow, 0.35, 0.18);
while (!Console.KeyAvailable)
{
s.MoveNDraw();
Thread.Sleep(30);
}
Console.ReadKey(true);
In-Class Talking Points
- Dont Ask Tell:
Programtells sprite what to do. - Encapsulation: draw/move logic stays inside sprite.
- By Stage 4,
x/yare no longer exposed (GetX/GetYremoved in Stage 3,SetX/SetYbecome private in Stage 4). - Accuracy: physics values are
double, rendering uses rounded int. - In the later optional version below, bounds default to current console size, but can be overridden in constructor.
- KIS: no inheritance, one class, one object, one behavior loop.
Final State (Copy/Paste): Arrow Keys Default + ESC loop
Sprite.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class Sprite
{
private double x;
private double y;
private double dX;
private double dY;
private string shape;
private ConsoleColor color;
private int maxX;
private int maxY;
private ConsoleKey leftKey;
private ConsoleKey rightKey;
private ConsoleKey upKey;
private ConsoleKey downKey;
// Default controls: arrow keys
public Sprite(double x, double y, string shape, ConsoleColor color, int maxX = -1, int maxY = -1)
{
this.shape = shape;
this.color = color;
this.maxX = maxX >= 0 ? maxX : Console.WindowWidth - 1;
this.maxY = maxY >= 0 ? maxY : Console.WindowHeight - 1;
this.leftKey = ConsoleKey.LeftArrow;
this.rightKey = ConsoleKey.RightArrow;
this.upKey = ConsoleKey.UpArrow;
this.downKey = ConsoleKey.DownArrow;
SetX(x);
SetY(y);
}
// Optional custom controls
public Sprite(
double x, double y, string shape, ConsoleColor color,
ConsoleKey leftKey, ConsoleKey rightKey, ConsoleKey upKey, ConsoleKey downKey,
int maxX = -1, int maxY = -1)
: this(x, y, shape, color, maxX, maxY)
{
this.leftKey = leftKey;
this.rightKey = rightKey;
this.upKey = upKey;
this.downKey = downKey;
}
private void SetX(double value)
{
if (value < 0) x = 0;
else if (value > maxX) x = maxX;
else x = value;
}
private void SetY(double value)
{
if (value < 0) y = 0;
else if (value > maxY) y = maxY;
else y = value;
}
private int GetXInt() => (int)Math.Round(x);
private int GetYInt() => (int)Math.Round(y);
public void SetDX(double value) => dX = value;
public void SetDY(double value) => dY = value;
public void DrawErase(ConsoleColor drawColor = ConsoleColor.Black)
{
Console.ForegroundColor = drawColor;
Console.SetCursorPosition(GetXInt(), GetYInt());
Console.Write(shape);
}
public void MoveNDraw()
{
DrawErase();
SetX(x + dX);
SetY(y + dY);
DrawErase(color);
}
public void HandleKey(ConsoleKey key)
{
if (key == leftKey) SetDX(-Math.Abs(dX) - 0.1);
else if (key == rightKey) SetDX(Math.Abs(dX) + 0.1);
else if (key == upKey) SetDY(-Math.Abs(dY) - 0.1);
else if (key == downKey) SetDY(Math.Abs(dY) + 0.1);
}
}
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.CursorVisible = false;
Console.Clear();
Sprite s = new Sprite(10, 5, "O", ConsoleColor.Yellow); // Arrow keys by default
s.SetDX(0.20);
s.SetDY(0.08);
bool running = true;
while (running)
{
while (!Console.KeyAvailable)
{
s.MoveNDraw();
Thread.Sleep(30);
}
ConsoleKey key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Escape)
running = false;
else
s.HandleKey(key);
}
Console.ResetColor();
Console.SetCursorPosition(0, Console.WindowHeight - 1);