Skip to content

Instantly share code, notes, and snippets.

@dallanlee
Last active August 5, 2021 00:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dallanlee/7bbcc9bac756ac93a4ec05b01dd5423a to your computer and use it in GitHub Desktop.
Save dallanlee/7bbcc9bac756ac93a4ec05b01dd5423a to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
function limitNumWithinRange(num, min = 1, max = 10) {
const parsed = parseInt(num)
return Math.min(Math.max(parsed, min), min)
}
const defaultColors = ['hsl(0, 90%, 55%)', 'hsl(90, 60%, 45%)', 'hsl(200, 90%, 55%)', 'hsl(270, 90%, 55%)']
const createTicTacToeMachine = (numPlayers = 2, boardSize = 3, gamesPerMatch = 1) => {
const minPlayers = 2
const maxPlayers = defaultColors.length > 4 ? 4 : defaultColors.length
numPlayers = limitNumWithinRange(numPlayers, minPlayers, maxPlayers)
function generateDefaultPlayers(num) {
return Array(num)
.fill()
.map((_, i) => ({
name: `Player ${i + 1}`,
color: defaultColors[i],
initialPosition: i,
wins: null,
}))
}
const players = generateDefaultPlayers(numPlayers)
function initializeBoard(size) {
const board = []
for (let i = 0; i < size; i++) {
const row = []
for (let j = 0; j < size; j++) {
row.push(null)
}
board.push(row)
}
return board
}
const board = initializeBoard(boardSize)
function fetchSavedMatches() {
return null
}
const savedMatches = fetchSavedMatches()
return Machine(
{
id: 'ticTacToeMachine',
initial: 'idle',
context: {
numPlayers,
players,
boardSize,
gamesPerMatch,
savedMatches,
abandonMatchOnSave: false,
gameStatus: {
board,
numMovesPlayed: 0,
savedNumMovesPlayed: 0,
},
},
states: {
idle: {
entry: ['fetchSavedMatches'],
on: {
PLAY_NEW_MATCH: {
target: 'playing',
},
CONFIGURE_MATCH_SETTINGS: {
target: 'configuringSettings',
},
VIEW_SAVED_MATCHES: [
{
cond: {
type: 'hasSavedMatches',
},
target: 'viewingSavedMatches',
},
{
target: 'fetchingSavedMatches',
actions: ['fetchSavedMatches'],
},
],
},
},
configuringSettings: {
on: {
SAVE_AND_PLAY: {
target: 'savingSettings',
actions: ['updateMatchSettings'],
},
RESTORE_DEFAULTS: {
target: 'configuringSettings',
actions: ['setDefaultMatchSettings'],
},
RANDOMIZE: {
target: 'configuringSettings',
actions: ['randomizeMatchSettings'],
},
ABORT: {
target: 'idle',
},
},
},
savingSettings: {
on: {
SUCCESS: {
target: 'playing',
actions: ['saveSettingsSuccess'],
},
FAIL: {
target: 'configuringSettings',
actions: ['saveSettingsFail'],
},
},
},
playing: {
on: {
PAUSE: {
target: 'viewingMatchStatus',
},
FINISHED: {
target: 'viewingMatchStatus',
actions: ['setGameWinner'],
},
},
},
viewingMatchStatus: {
on: {
REMATCH: {
cond: {
type: 'matchOver',
},
target: 'playing',
actions: ['playRematch'],
},
EXTEND_MATCH: {
cond: {
type: 'matchOver',
},
target: 'playing',
actions: ['increaseGamesPerMatch(1)'],
},
REVIEW_GAME: {
cond: {
type: 'gameOver',
},
target: 'reviewingGame',
},
RESUME: [
{
cond: {
type: 'gameOver',
},
target: 'playing',
actions: ['playNextGame'],
},
{
target: 'playing',
},
],
SAVE: {
cond: {
type: 'hasUnsavedProgress',
},
target: 'savingMatch',
actions: ['saveMatch'],
},
QUIT: [
{
cond: {
type: 'hasUnsavedProgress',
},
target: 'confirmingAbandonProgress',
},
{
target: 'idle',
actions: ['abandonMatch'],
},
],
},
},
confirmingAbandonProgress: {
on: {
CONFIRM: {
target: 'idle',
actions: ['abandonMatch'],
},
SAVE_AND_QUIT: {
target: 'savingMatch',
actions: ['saveMatch', 'abandonMatchOnSave'],
},
ABORT: {
target: 'viewingMatchStatus',
},
},
},
reviewingGame: {
on: {
ABORT: {
target: 'viewingMatchStatus',
},
},
},
savingMatch: {
on: {
SUCCESS: [
{
cond: {
type: 'abandonMatchOnSave',
},
target: 'idle',
actions: ['saveMatchSuccess'],
},
{
target: 'viewingMatchStatus',
actions: [
// update gameStatus.numSavedMovesPlayed
assign({
gameStatus: (ctx, evt) => {
return { ...ctx, savedNumMovesPlayed: ctx.gameStatus.numMovesPlayed }
},
}),
'saveMatchSuccess',
],
},
],
FAIL: {
target: 'viewingMatchStatus',
actions: ['saveMatchFail'],
},
},
},
viewingSavedMatches: {
always: [
{
cond: {
type: '!hasSavedMatches',
},
target: 'idle',
actions: ['noSavedMatches'],
},
],
on: {
LOAD: {
target: 'restoringMatch',
actions: ['setupMatch(selectedMatch)'],
},
RENAME: {
target: 'renamingMatch',
actions: ['renameMatch(selectedMatch)'],
},
DELETE: {
target: 'deletingMatch',
actions: ['deleteMatch(selectedMatch)'],
},
REFETCH: {
target: 'fetchingSavedMatches',
actions: ['fetchSavedMatches'],
},
ABORT: {
target: 'idle',
},
},
},
restoringMatch: {
on: {
SUCCESS: {
target: 'playing',
},
FAIL: {
target: 'viewingSavedMatches',
actions: ['restoreMatchFail'],
},
},
},
renamingMatch: {
on: {
SUCCESS: {
target: 'viewingSavedMatches',
actions: 'renameMatchSuccess',
},
FAIL: {
target: 'viewingSavedMatches',
actions: ['renameMatchFail'],
},
},
},
deletingMatch: {
on: {
SUCCESS: {
target: 'viewingSavedMatches',
actions: ['deleteMatchSuccess'],
},
FAIL: {
target: 'viewingSavedMatches',
actions: ['deleteMatchFail'],
},
},
},
fetchingSavedMatches: {
on: {
SUCCESS: {
target: 'viewingSavedMatches',
actions: ['fetchSavedMatchesSuccess'],
},
FAIL: {
target: 'viewingSavedMatches',
actions: ['fetchSavedMatchesFail'],
},
},
},
},
},
{
actions: {},
guards: {
hasSavedMatches: (ctx, evt) => {
return ctx.savedMatches?.length > 0
},
currentProgressSaved: (ctx, evt) => {
// NOTE: This guard doesn't account for an undo feature. With undo,
// a player could undo a move and make a different move without ever
// saving but still get `true` here.
return (
ctx.gameStatus.savedNumMovesPlayed > 0 &&
ctx.gameStatus.numMovesPlayed === ctx.gameStatus.savedNumMovesPlayed
)
},
hasUnsavedProgress: (ctx, evt) => {
return ctx.gameStatus.numMovesPlayed !== ctx.gameStatus.savedNumMovesPlayed
},
},
},
)
}
createTicTacToeMachine()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment