Purpose
Build one tiny Sprite class live, no inheritance, start with Java-style getters/setters, then lock down API so the object does the work.
Core message for teachers:
Programshould not ask for internals and draw by itself.Programshould tell the object:sprite.MoveNDraw();
Time Box
18-25minutes live coding.- Keep one sprite only.
- Keep one-char shape (
"O"or"@").
Stage 0 - Setup
Create a new console app with two files:
Program.csSprite.cs
Minimal Program.cs start:
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.CursorVisible = false;
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;
private double y;
private double dX;
private double dY;
private string shape;
private ConsoleColor color;
private int maxX;
private int maxY;
public Sprite(double x, double y, string shape, ConsoleColor color, int maxX = -1, int maxY = -1)
{
this.x = x;
this.y = y;
this.dX = 0;
this.dY = 0;
this.shape = shape;
this.color = color;
this.maxX = maxX;
this.maxY = maxY;
}
public double GetX()
{
return x;
}
public void SetX(double value)
{
x = value;
}
public double GetY()
{
return y;
}
public void SetY(double value)
{
y = value;
}
public double GetDX()
{
return dX;
}
public void SetDX(double value)
{
dX = value;
}
public double GetDY()
{
return dY;
}
public void SetDY(double value)
{
dY = value;
}
public string GetShape()
{
return shape;
}
public void SetShape(string value)
{
shape = value;
}
public ConsoleColor GetColor()
{
return color;
}
public void SetColor(ConsoleColor value)
{
color = value;
}
public int GetMaxX()
{
return maxX;
}
public void SetMaxX(int value)
{
maxX = value;
}
public int GetMaxY()
{
return maxY;
}
public void SetMaxY(int value)
{
maxY = value;
}
}
Teacher line:
- This is the quick first pass exactly as generated and lightly renamed.
Stage 2 - Modify the Vanilla Code for Game Rules
Now edit the generated constructor/getters/setters (do not rewrite from scratch).
At this stage, hide position internals: make SetX/SetY private and remove public GetX/GetY.
public Sprite(double x, double y, string shape, ConsoleColor color, int maxX = -1, int maxY = -1)
{
- this.x = x;
- this.y = y;
this.dX = 0;
this.dY = 0;
this.shape = shape;
this.color = color;
- this.maxX = maxX;
- this.maxY = maxY;
+ this.maxX = maxX >= 0 ? maxX : Console.WindowWidth - 1;
+ this.maxY = maxY >= 0 ? maxY : Console.WindowHeight - 1;
+ SetX(x);
+ SetY(y);
}
-public double GetX()
-{
- return x;
-}
-
-public double GetY()
-{
- return y;
-}
-
+private void SetX(double value)
{
- x = value;
+ if (value < 0) x = 0;
+ else if (value > maxX) x = maxX;
+ else x = value;
}
private void SetY(double value)
{
- y = value;
+ if (value < 0) y = 0;
+ else if (value > maxY) y = maxY;
+ else y = value;
}
+
+private int GetXInt()
+{
+ return (int)Math.Round(x);
+}
+
+private int GetYInt()
+{
+ return (int)Math.Round(y);
+}
Teacher line:
- We use
doublefor movement accuracy, but console draw needs rounded ints. Programcannot directly set or readx/yanymore; it must tell the sprite what to do.
Stage 3 - Add Draw/Erase as One Method
Now give the object its own console behavior.
+/// <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);
+s.DrawErase(ConsoleColor.Green);
Teacher line:
- Default argument is black, so same method can erase.
Stage 4 - Add MoveNDraw (Dont Ask Tell)
Main behavior: erase old, move by dX/dY, draw new.
+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.
Stage 5 - Program Loop
Very small demo loop for class.
+Sprite s = new Sprite(10, 5, "O", ConsoleColor.Yellow);
+s.SetDX(0.35);
+s.SetDY(0.18);
+
+while (!Console.KeyAvailable)
+{
+ s.MoveNDraw();
+ Thread.Sleep(30);
+}
Optional quick key to stop:
Console.ReadKey(true);
Stage 6 - Optional Refactor to bool API
If you want to hide color choice from callers even more:
-public void DrawErase(ConsoleColor drawColor = ConsoleColor.Black)
+public void DrawAndErase(bool erase = false)
{
- Console.ForegroundColor = drawColor;
+ Console.ForegroundColor = erase ? ConsoleColor.Black : color;
Console.SetCursorPosition(GetXInt(), GetYInt());
Console.Write(shape);
}
public void MoveNDraw()
{
- DrawErase();
+ DrawAndErase(true);
SetX(x + dX);
SetY(y + dY);
- DrawErase(color);
+ DrawAndErase();
}
Teacher note:
- For live coding speed today, you can skip this stage.
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, 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);
+}
+
+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);
+}
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)
Recommended final for the live lesson (faster): keep DrawErase(ConsoleColor ...).
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
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;
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;
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;
}
public void SetDX(double value) { dX = value; }
public void SetDY(double value) { dY = value; }
public void SetColor(ConsoleColor value) { color = value; }
public void SetShape(string value) { shape = value; }
private int GetXInt() { return (int)Math.Round(x); }
private int GetYInt() { return (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
13
14
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.CursorVisible = false;
Sprite s = new Sprite(10, 5, "O", ConsoleColor.Yellow);
s.SetDX(0.35);
s.SetDY(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.
- In Stage 2,
x/yare no longer exposed (SetX/SetYbecome private,GetX/GetYremoved). - Accuracy: physics values are
double, rendering uses rounded int. - Bounds default to current console size, but can be overridden in constructor.
- KIS: no inheritance, one class, one object, one behavior loop.
Quick Pre-Lesson Alignment Questions
- For today, do you want final API to stay
DrawErase(ConsoleColor)or switch toDrawAndErase(bool erase = false)at the end? - In the live demo, stop at auto-movement only, or include the later key-constructor extension?
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() { return (int)Math.Round(x); }
private int GetYInt() { return (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);