Skip to content

Commit

Permalink
Release v1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
etmendz committed Nov 16, 2023
1 parent dd9b64e commit fee1ed9
Show file tree
Hide file tree
Showing 21 changed files with 432 additions and 139 deletions.
65 changes: 52 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,39 @@ The design allows the game developer to focus on implementing the game play and

Thus, for example, in a game console app's program main entry point, the developer can simply code:

new GameConsole<GameUI, GamePlay, ConsoleKey, bool>(
new GameConsole<GameUI, GamePlay>(
"Game name",
"All rights reserved.",
"Game description.",
"Press [Esc] anytime to exit.",
GamePlayReadyMode.WhileReady
).Play();

## IGamePlay<in TActionIn, out TActionOut>
Defines a game play designed to align with the basic construct's Start(), Action(), Continue(), GameOver() and End() methods.
## GameActionMode
Specifies the game action mode if for GamePlay, GamePause or GameStop.

## GameActionType
Specifies the game action type if for Control, Response, Navigation or Other.

## GameActionInfo
Represents a game action, which has an input, an output and/or a result for the given game action mode and game action type.

The default game action mode is GamePlay.

The game play Action() is defined by the *in* and *out* generic types passed to IGamePlay.
The default game action type is Control.

Typically, an IGameUI.Action() creates an instance of a GameActionInfo to pass to IGamePlay.Action() for execution, evaluation or processing.

## IGamePlay and IGamePlayAsync
Defines a game play designed to align with the basic construct's Start(), Action(), Continue(), GameOver() and End() methods.

The game play is essentially the actual game.

When implemented sans any UI-specific aspects, the game play can be re-used in different UI/UX platforms.

## IGameUI<TGamePlay, in TActionIn, out TActionOut>
IGamePlayAsync is provided for async implementations.

## IGameUI\<TGamePlay\> and IGameUIAsync\<TGamePlay\>
Defines a game UI designed to align with the basic construct's Start(), Action(), Continue(), GameOver() and End() methods.

IGameUI accepts a type that implements IGamePlay, and provides additional methods to Render() and Refresh().
Expand All @@ -50,7 +65,9 @@ An implementation must define a parameterless constructor that can initialize an

Implementations can be UI/UX platform specific.

## IGameFlow
IGameUIAsync is provided for async implementations.

## IGameFlow and IGameFlowAsync
Defines a game flow designed to align with the basic flow's Play(), Ready(), Set() and Go() methods.

Play() implements the basic flow.
Expand All @@ -59,10 +76,7 @@ Go() implements the basic construct.

Implementations can be UI/UX platform specific.

## GameRandomizer
Defines a game randomizer to generate random numbers.

Methods are provided to get the next random number, the next random number below a limit, and the next random number within a minimum/maximum range.
IGameFlowAsync is provided for async implementations.

## GamePlayReadyMode
Specifies the game play ready mode or how the game flows to consume the Ready(), Set() and Go() methods.
Expand All @@ -79,6 +93,8 @@ An example is a dice guessing game, where the player guesses and rolls the dice:
- Opting to continue simply continues the same game;
- Otherwise, the game ends and closes.

See: [GameConsoleDice](https://github.com/etmendz/game-console-dice)

### WhileReady
Allow starting more than one game UI instance per launch.

Expand All @@ -92,7 +108,9 @@ An example is 2048, where the player performs moves to reach a goal:
2. If the player runs out of moves, then the game is over:
- Then the game prompts the player to start a new game.

## GameConsole<TGameUI, TGamePlay, TActionIn, TActionOut>
See: [GameConsole2048](https://github.com/etmendz/game-console-2048)

## GameConsole\<TGameUI, TGamePlay\>
GameConsole implements IGameFlow for game console apps.

GameConsole accepts a type that implements IGameUI, which requires a type that implements IGamePlay.
Expand Down Expand Up @@ -157,7 +175,7 @@ The code pattern can be described as follows:
- Action() refreshes the UI, prompts for action input and processes the result
- Continue() evaluates the game result and prompts for control input (to try again?)
- GameOver() checks for the game over state
- End() shows the game result.
- End() shows the game result and ends the game.

## GameConsole3Seconds
[GameConsole3Seconds](https://github.com/etmendz/game-library/tree/main/examples/GameConsole3Seconds) challenges the player to stop the clock at 3 seconds flat.
Expand All @@ -176,11 +194,32 @@ The code pattern can be described as follows:
- Action() refreshes the UI, prompts for action input and processes the result
- Continue() evaluates the game result, shows the game result and prompts for control input (to try again?); if the player opts to continue, the clock is restarted and the UI is re-rendered.
- GameOver() checks for the game over state (if the player opted to quit)
- End() in effect, "acknowledges" that the player quits; essentially, does nothing.
- End() ends the game.

# Boilerplate
Boilerplate template codes are available in the repo's [boilerplate/](https://github.com/etmendz/game-library/tree/main/boilerplate) subfolder.

# Release Notes

## v1.1.0
This is the first release version, published after Microsoft released .NET 8.

- Breaking: GameRandomizer deprecated. Directly use [System.Random.Shared](https://learn.microsoft.com/en-us/dotnet/api/system.random.shared) instead.
- Breaking: IGamePlay.Action() is no longer variant, instead defined as: bool Action(GameActionInfo).
- Breaking: IGamePlay no longer needs variant types for Action().
- Breaking: IGameUI no longer needs variant types for IGamePlay.
- Breaking: GameConsole no longer needs variant types for TGameUI and TGamePlay.
- New: Introduced GameActionMode and GameActionType enumerations.
- New: Introduced GameActionInfo, a class representing a game action.
- New: Introduced IGamePlayAsync, IGameUIAsync and IGameFlowAsync.
- New: GameConsoleUX adds GetParsableEntry(), which can block/loop until a valid date or numeric value is entered.
- Updated: Game.cs.txt boilerplate template updated in line with GameConsole's breaking changes.
- Updated: Example projects codes updated in line with the breaking changes listed above.
- Updated: Documentation updated.

## v1.0.x
These are the preview versions, implemented using .NET 8 preview versions.

---

(c) Mendz, etmendz. All rights reserved.
16 changes: 3 additions & 13 deletions boilerplate/Game.cs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,18 @@
* Replace "game-console-app-name" with your game console app project's namespace.
* Find and replace the appropriate generic types with actual types in the game project.
* Use this template to wrap GameConsole with game initialization and setup codes.
*
* Thus, the program main entry point can use the following boilerplate template to call Play():
*
* new Game().Play();
* This works with Program.cs.txt.
*/

namespace game-console-app-name;

/// <summary>
/// Wraps GameConsole with game initialization and setup codes.
/// </summary>
/// <remarks>
/// The program main entry point can use the following boilerplate template to call Play():
/// <code>
/// new Game().Play();
/// </code>
/// </remarks>
internal class Game : GameConsole<game-ui-name, game-play-name, action-in-name, action-out-name>
internal class Game : GameConsole<game-ui-name, game-play-name>
{
public Game()
: base(
"Game name",
: base("Game name",
"All rights reserved.",
"Game description.",
"Press [Esc] anytime to exit.",
Expand Down
2 changes: 1 addition & 1 deletion boilerplate/Program.cs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* To use, save this template to a new .cs file (ex. Program.cs) in your project.
* Replace "game-console-app-name" with your game console app project's namespace.
* This works only if a Game class is also implemented (see Game.cs.txt).
* This works with Game.cs.txt.
*/
namespace game-console-app-name;

Expand Down
2 changes: 1 addition & 1 deletion examples/GameConsole3Guesses/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace GameConsole3Guesses;

internal class Game : GameConsole<GameUI, GamePlay, int, bool>
internal class Game : GameConsole<GameUI, GamePlay>
{
public Game()
: base("GameConsole3Guesses",
Expand Down
8 changes: 4 additions & 4 deletions examples/GameConsole3Guesses/GamePlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace GameConsole3Guesses;

internal class GamePlay : IGamePlay<int, bool>
internal class GamePlay : IGamePlay
{
public int Secret { get; private set; }

Expand All @@ -16,15 +16,15 @@ internal class GamePlay : IGamePlay<int, bool>

public bool Start()
{
Secret = GameRandomizer.Next(1, 11);
Secret = Random.Shared.Next(1, 11);
IsWon = false;
Tries = 0;
return true;
}

public bool Action(int action)
public bool Action(GameActionInfo gameActionInfo)
{
if (action == Secret) IsWon = true;
if (gameActionInfo.GetInputAs<int>() == Secret) IsWon = true;
Tries++;
return true;
}
Expand Down
23 changes: 14 additions & 9 deletions examples/GameConsole3Guesses/GameUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@

namespace GameConsole3Guesses;

internal class GameUI : IGameUI<GamePlay, int, bool>
internal class GameUI : IGameUI<GamePlay>
{
/// <summary>
/// Note that a game can have different instances of GameConsoleUX for different game action mode and type combinations.
/// For example, one instance can have EscExit = true, and another instance with EscExit = false.
/// </summary>
private readonly GameConsoleUX _gameConsoleUX = new();

public GamePlay GamePlay { get; set; }

public GameUI() => GamePlay = new();
Expand All @@ -25,25 +31,24 @@ public bool Action()
Console.WriteLine();
Console.Write($"Guess #{GamePlay.Tries + 1}: ");
Console.CursorVisible = true;
int guess;
while (true)
GameActionInfo gameActionInfo = new()
{
if (Int32.TryParse(Console.ReadLine(), out guess)) break;
}
ActionType = GameActionType.Response,
Input = GameConsoleUX.GetParsableEntry<int>()
};
Console.CursorVisible = false;
return GamePlay.Action(guess);
return GamePlay.Action(gameActionInfo);
}

public bool Continue()
{
bool isOK = false;
if (GamePlay.Continue())
{
Console.WriteLine();
Console.WriteLine("Try again? (Y/N): ");
isOK = new GameConsoleUX().GetYN() == ConsoleKey.Y;
return _gameConsoleUX.GetYN() == ConsoleKey.Y;
}
return isOK;
return true;
}

public void End()
Expand Down
2 changes: 1 addition & 1 deletion examples/GameConsole3Seconds/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace GameConsole3Seconds;

internal class Game : GameConsole<GameUI, GamePlay, ConsoleKey, bool>
internal class Game : GameConsole<GameUI, GamePlay>
{
public Game()
: base("GameConsole3Seconds",
Expand Down
4 changes: 2 additions & 2 deletions examples/GameConsole3Seconds/GamePlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace GameConsole3Seconds;

internal class GamePlay : IGamePlay<ConsoleKey, bool>
internal class GamePlay : IGamePlay
{
public Stopwatch Stopwatch { get; private set; } = new();

Expand All @@ -21,7 +21,7 @@ public bool Start()
return Stopwatch.IsRunning;
}

public bool Action(ConsoleKey action)
public bool Action(GameActionInfo gameActionInfo)
{
Stopwatch.Stop();
long elapsed = Stopwatch.ElapsedMilliseconds;
Expand Down
18 changes: 12 additions & 6 deletions examples/GameConsole3Seconds/GameUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@

namespace GameConsole3Seconds;

internal class GameUI : IGameUI<GamePlay, ConsoleKey, bool>
internal class GameUI : IGameUI<GamePlay>
{
/// <summary>
/// Note that a game can have different instances of GameConsoleUX for different game action mode and type combinations.
/// For example, one instance can have EscExit = true, and another instance with EscExit = false.
/// </summary>
private readonly GameConsoleUX _gameConsoleUX = new();

private readonly HashSet<ConsoleKeyInfo> _validKeyInfos = [new(' ', ConsoleKey.Spacebar, false, false, false)];

private bool _refresh = false;

public GamePlay GamePlay { get; set; }
Expand All @@ -34,15 +42,13 @@ public void Render()
public bool Action()
{
ConsoleKeyInfo ? cki = null;
GameConsoleUX gameUX = new();
HashSet<ConsoleKeyInfo> validKeyInfos = [new(' ', ConsoleKey.Spacebar, false, false, false)];
while (cki is null)
{
Refresh();
cki = gameUX.GetKeyInfo(validKeyInfos);
cki = _gameConsoleUX.GetKeyInfo(_validKeyInfos);
}
Console.WriteLine();
return GamePlay.Action(cki.Value.Key);
return GamePlay.Action(new() { Input = cki.Value.Key });
}

public bool Continue()
Expand All @@ -62,7 +68,7 @@ public bool Continue()
Console.ForegroundColor = foregroundColor;
Console.WriteLine();
Console.WriteLine("Try again? (Y/N): ");
if (new GameConsoleUX().GetYN() == ConsoleKey.Y)
if (_gameConsoleUX.GetYN() == ConsoleKey.Y)
{
_refresh = false; // Turn off refresh mode...
GamePlay.Continue();
Expand Down
61 changes: 61 additions & 0 deletions src/GameLibrary/GameActionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace GameLibrary;

/// <summary>
/// Represents a game action.
/// </summary>
public class GameActionInfo
{
/// <summary>
/// Gets or sets the action mode.
/// </summary>
public GameActionMode ActionMode { get; set; } = GameActionMode.GamePlay;

/// <summary>
/// Gets or sets the action type.
/// </summary>
public GameActionType ActionType { get; set; } = GameActionType.Control;

/// <summary>
/// Gets or sets the game action input.
/// </summary>
public object? Input { get; set; }

/// <summary>
/// Gets or sets the game action output.
/// </summary>
public object? Output { get; set; }

/// <summary>
/// Gets or sets the game action result.
/// </summary>
public object? Result { get; set; }

/// <summary>
/// Gets the input as the type specified. Basically, a casting mnemonic.
/// </summary>
/// <typeparam name="T">The type to cast to.</typeparam>
/// <returns>The input cast to type T.</returns>
public virtual T? GetInputAs<T>() => GetValueAs<T>(Input);

/// <summary>
/// Gets the output as the type specified. Basically, a casting mnemonic.
/// </summary>
/// <typeparam name="T">The type to cast to.</typeparam>
/// <returns>The output cast to type T.</returns>
public virtual T? GetOutputAs<T>() => GetValueAs<T>(Output);

/// <summary>
/// Gets the result as the type specified. Basically, a casting mnemonic.
/// </summary>
/// <typeparam name="T">The type to cast to.</typeparam>
/// <returns>The result cast to type T.</returns>
public virtual T? GetResultAs<T>() => GetValueAs<T>(Result);

/// <summary>
/// Gets the value as the type specified. Basically, a casting mnemonic.
/// </summary>
/// <typeparam name="T">The type to cast to.</typeparam>
/// <param name="value">The value to cast.</param>
/// <returns>The value cast to type T.</returns>
protected static T? GetValueAs<T>(object? value) => (T?)value;
}

0 comments on commit fee1ed9

Please sign in to comment.