Skip to content

Instantly share code, notes, and snippets.

@sethladd
Created April 27, 2025 21:10
Show Gist options
  • Save sethladd/4a33518985bb6bd08e664c435d4ed087 to your computer and use it in GitHub Desktop.
Save sethladd/4a33518985bb6bd08e664c435d4ed087 to your computer and use it in GitHub Desktop.
performance art
import 'package:flutter/material.dart'; // Or wherever ActionStateType comes from
// --- State Pattern Implementation ---
// 0. Define the result type (e.g., an enum) if needed for UI logic
enum ActionStateType { ready, answering, complete }
// 1. Define the State Interface (or abstract class)
abstract class GameState {
// Method for the state to determine what the *current* state should be
// based on the context's data. It returns the type of the state.
// It can also trigger transitions by telling the context to change its state object.
ActionStateType handleState(GameContext context);
// Optional: Define an associated enum type for easy checking
ActionStateType get type;
}
// 2. Define the Context class
class GameContext {
// Data influencing the state
int messageCount;
bool isGuessed;
// Reference to the current state object
late GameState _currentState;
// Constructor initializes data and sets the initial state object
GameContext({required this.messageCount, required this.isGuessed}) {
// Set initial state based on initial data
_currentState = _getCorrespondingStateObject();
print("Initial state: ${_currentState.runtimeType}");
}
// Method to update the data and potentially trigger a state transition
void updateData({required int messageCount, required bool isGuessed}) {
this.messageCount = messageCount;
this.isGuessed = isGuessed;
// Ask the current state object to handle the (potentially new) situation
// This might lead to the state object calling back context.setState()
_currentState.handleState(this);
}
// Method called by State objects to change the context's current state
void setState(GameState newState) {
// Only transition if the new state is actually different
if (_currentState.runtimeType != newState.runtimeType) {
print("Transitioning from ${_currentState.runtimeType} to ${newState.runtimeType}");
_currentState = newState;
}
}
// Helper to get the state object corresponding to the logical state type
GameState _getCorrespondingStateObject() {
final stateType = currentActionStateType; // Calculate the logical type
switch (stateType) {
case ActionStateType.ready:
return ReadyState();
case ActionStateType.answering:
return AnsweringState();
case ActionStateType.complete:
return CompleteState();
}
}
// Public getter to easily access the *logical* current state type (enum)
ActionStateType get currentActionStateType {
if (messageCount == 1) return ActionStateType.ready;
if (isGuessed || messageCount >= 20) return ActionStateType.complete;
return ActionStateType.answering;
}
// Optional: Expose the state object itself if needed
GameState get currentStateObject => _currentState;
}
// 3. Implement Concrete States
class ReadyState implements GameState {
@override
ActionStateType get type => ActionStateType.ready;
@override
ActionStateType handleState(GameContext context) {
// Check if we should transition *out* of ReadyState
if (context.messageCount != 1) {
if (context.isGuessed || context.messageCount >= 20) {
context.setState(CompleteState()); // Tell context to change state
return ActionStateType.complete;
} else {
context.setState(AnsweringState()); // Tell context to change state
return ActionStateType.answering;
}
}
// Otherwise, stay in Ready state
return ActionStateType.ready;
}
}
class AnsweringState implements GameState {
@override
ActionStateType get type => ActionStateType.answering;
@override
ActionStateType handleState(GameContext context) {
// Check if we should transition *out* of AnsweringState
if (context.messageCount == 1) {
context.setState(ReadyState());
return ActionStateType.ready;
} else if (context.isGuessed || context.messageCount >= 20) {
context.setState(CompleteState());
return ActionStateType.complete;
}
// Otherwise, stay in Answering state
return ActionStateType.answering;
}
}
class CompleteState implements GameState {
@override
ActionStateType get type => ActionStateType.complete;
@override
ActionStateType handleState(GameContext context) {
// Check if we should transition *out* of CompleteState
// This might happen if conditions change drastically (e.g., reset)
if (context.messageCount == 1) {
context.setState(ReadyState());
return ActionStateType.ready;
} else if (!(context.isGuessed || context.messageCount >= 20)) {
// No longer meets completion criteria
context.setState(AnsweringState());
return ActionStateType.answering;
}
// Otherwise, stay in Complete state
return ActionStateType.complete;
}
}
// --- Example Usage in a Flutter Widget's State ---
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
// Assume these are managed elsewhere in your state
List<String> messages = ["Initial Message"];
bool guessed = false;
// Hold the GameContext instance
late GameContext _gameContext;
@override
void initState() {
super.initState();
// Initialize the context with the initial data
_gameContext = GameContext(
messageCount: messages.length,
isGuessed: guessed,
);
}
// Example method to simulate adding a message
void _addMessage(String msg) {
setState(() {
messages.add(msg);
// Update the context with the new data
_gameContext.updateData(
messageCount: messages.length,
isGuessed: guessed,
);
});
}
// Example method to simulate guessing correctly
void _guessCorrectly() {
setState(() {
guessed = true;
// Update the context
_gameContext.updateData(
messageCount: messages.length,
isGuessed: guessed,
);
});
}
@override
Widget build(BuildContext context) {
// Get the simple enum state type from the context for UI logic
final ActionStateType actionState = _gameContext.currentActionStateType;
// You might also access the state object directly if needed:
// final GameState currentStateObj = _gameContext.currentStateObject;
print("Build method: Current Action State Type: $actionState");
// Use the actionState enum to control UI...
String statusText = "Unknown";
switch(actionState) {
case ActionStateType.ready: statusText = "Ready to Play!"; break;
case ActionStateType.answering: statusText = "Answering (${messages.length}/20)"; break;
case ActionStateType.complete: statusText = "Game Complete!"; break;
}
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('State Pattern Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(statusText, style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 20),
ElevatedButton(
// Disable adding messages if complete or already >= 20
onPressed: actionState == ActionStateType.complete || messages.length >= 20
? null
: () => _addMessage("New message ${messages.length + 1}"),
child: const Text('Add Message'),
),
const SizedBox(height: 10),
ElevatedButton(
// Disable guessing if already complete
onPressed: actionState == ActionStateType.complete
? null
: () => _guessCorrectly(),
child: const Text('Guess Correctly'),
),
],
),
),
),
);
}
}
void main() {
runApp(const MyWidget());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment