Skip to content

Instantly share code, notes, and snippets.

@michael-wolfenden
Created August 28, 2018 06:10
Show Gist options
  • Save michael-wolfenden/b91bff68adef902063ffe7a4f86c63a7 to your computer and use it in GitHub Desktop.
Save michael-wolfenden/b91bff68adef902063ffe7a4f86c63a7 to your computer and use it in GitHub Desktop.
Port of F# Tic-Tac-Toe
namespace TicTacToe
{
namespace Schema
{
public static class Constructors
{
public static One One() => OneThroughThree.One;
public static Two Two() => OneThroughThree.Two;
public static Three Three() => OneThroughThree.Three;
public static Unspecified Unspecified = new Unspecified();
public static Letter Letter(Letters letters) => new Letter(letters);
public static Position Position(OneThroughThree column, OneThroughThree row) => new Position(column, row);
public static Row Row(Value v1, Value v2, Value v3) => Schema.Row.New((v1, v2, v3));
public static Board Board(Row r1, Row r2, Row r3) => new Board((r1, r2, r3));
public static Board EmptyBoard() => new Board(
(Row(Unspecified, Unspecified, Unspecified),
Row(Unspecified, Unspecified, Unspecified),
Row(Unspecified, Unspecified, Unspecified)));
public static Move Move(Position at, Letter place) => new Move(at, place);
public static Outcome Winner(Letters letters) => new Winner(letters);
public static Outcome Draw() => new Draw();
public static Outcome NoneYet() => new NoneYet();
public static GameState initialGameState() => GameState(EmptyBoard(), Letters.X);
public static GameState GameState(Board board, Letters letters) => new GameState(board, letters);
}
public static class Extensions
{
public static Row toRow(this (Value, Value, Value) @this) => new Row(@this);
}
}
}
using LanguageExt;
using System;
using static TicTacToe.Schema.Constructors;
using static LanguageExt.Prelude;
using System.Linq;
using System.Collections.Generic;
using System.Text;
namespace TicTacToe
{
namespace Schema
{
public static class Ops
{
public static Value select(Board board, Position position)
{
var (col, row) = position;
var (row1, row2, row3) = board;
switch (row)
{
case One _:
var (x1, y1, z1) = row1;
if (col is One) return x1;
if (col is Two) return y1;
if (col is Three) return z1;
break;
case Two _:
var (x2, y2, z2) = row2;
if (col is One) return x2;
if (col is Two) return y2;
if (col is Three) return z2;
break;
case Three _:
var (x3, y3, z3) = row3;
if (col is One) return x3;
if (col is Two) return y3;
if (col is Three) return z3;
break;
}
throw new ArgumentOutOfRangeException();
}
public static Board set(Value value, Board board, Position position)
{
var (col, row) = position;
var (row1, row2, row3) = board;
switch (row)
{
case One _:
if (col is One) return board.With(r1: row1.With(v1: value));
if (col is Two) return board.With(r1: row1.With(v2: value));
if (col is Three) return board.With(r1: row1.With(v3: value));
break;
case Two _ when col is One: return board.With(r2: row2.With(v1: value));
case Two _ when col is Two: return board.With(r2: row2.With(v2: value));
case Two _ when col is Three: return board.With(r2: row2.With(v3: value));
case Three _ when col is One: return board.With(r3: row3.With(v1: value));
case Three _ when col is Two: return board.With(r3: row3.With(v2: value));
case Three _ when col is Three: return board.With(r3: row3.With(v3: value));
}
throw new ArgumentOutOfRangeException();
}
public static Board modify(Func<Value, Value> f, Board board, Position position) =>
set(f(select(board, position)), board, position);
public static Board placePieceIfCan(Letters letter, Board board, Position position) =>
modify(value =>
value is Unspecified ? Letter(letter) : value,
board,
position);
public static Option<Board> makeMove(Board board, Move move)
{
if (select(board, move.At) is Unspecified)
return Some(placePieceIfCan(move.Place.Value, board, move.At));
return None;
}
public static IEnumerable<(Position, Position, Position)> waysToWin()
{
yield return (Position(One(), One()), Position(One(), Two()), Position(One(), Three()));
yield return (Position(Two(), One()), Position(Two(), Two()), Position(Two(), Three()));
yield return (Position(Three(), One()), Position(Three(), Two()), Position(Three(), Three()));
yield return (Position(One(), One()), Position(Two(), One()), Position(Three(), One()));
yield return (Position(One(), Two()), Position(Two(), Two()), Position(Three(), Two()));
yield return (Position(One(), Three()), Position(Two(), Three()), Position(Three(), Three()));
yield return (Position(One(), One()), Position(Two(), Two()), Position(Three(), Three()));
yield return (Position(Three(), One()), Position(Two(), Two()), Position(One(), Three()));
}
static readonly OneThroughThree[] Values = new OneThroughThree[] { new One(), new Two(), new Three() };
public static IEnumerable<Position> cells() =>
from row in Values
from col in Values
select Position(col, row);
public static Option<Letters> winner(Board board)
{
Value selectFromBoard(Position position) => select(board, position);
Option<Letters> selectLetterFromBoard(Position position) =>
selectFromBoard(position) is Letter letter ? Some(letter.Value) : None;
var winPaths = waysToWin()
.Select(way => way.Map(selectLetterFromBoard, selectLetterFromBoard, selectLetterFromBoard));
var winners = from way in winPaths
from v1 in way.Item1
from v2 in way.Item2
from v3 in way.Item3
where v1 == v2 && v2 == v3
select v1;
return winners.ToOption();
}
public static bool slotsRemaining(Board board) =>
cells().Select(p => select(board, p)).OfType<Unspecified>().Any();
public static Outcome outcome(Board board)
{
var win = winner(board);
var slotsLeft = slotsRemaining(board);
return win.Match(
Some: letter => Winner(letter),
None: () => (slotsLeft == false ? Draw() : NoneYet()));
}
public static string renderValue(Value letter)
{
switch (letter)
{
case Letter l when l.Value == Letters.O: return "O";
case Letter l when l.Value == Letters.X: return "X";
case Unspecified _: return " ";
}
throw new ArgumentOutOfRangeException();
}
public static Letters otherPlayer(Letters letters) =>
letters == Letters.O ? Letters.X : Letters.O;
public static string render(Board board)
{
var sb = new StringBuilder();
foreach (var row in board)
{
sb.AppendLine($"{renderValue(row.Value.Item1)}|{renderValue(row.Value.Item2)}|{renderValue(row.Value.Item3)}");
sb.AppendLine("-----");
}
return sb.ToString();
}
public static Option<OneThroughThree> parseOneThroughThree(string str)
{
switch (str)
{
case "1": return Some<OneThroughThree>(One());
case "2": return Some<OneThroughThree>(Two());
case "3": return Some<OneThroughThree>(Three());
}
return None;
}
public static Option<Position> parseMove(string str)
{
var tokens = str.Split(' ');
if (tokens.Length == 2)
{
return from row in parseOneThroughThree(tokens[0])
from col in parseOneThroughThree(tokens[1])
select Position(col, row);
}
return None;
}
public static Move readMoveTo(Letters letters)
{
return parseMove(Console.ReadLine())
.Match(
Some: position => Move(position, Letter(letters)),
None: () =>
{
Console.WriteLine("Bad Move! Please input row and column numbers");
return readMoveTo(letters);
});
}
public static Board nextMoveIo(Board board, Letters letters)
{
return makeMove(board, readMoveTo(letters))
.Match(
Some: identity,
None: () =>
{
Console.WriteLine("Bad move! Position is occupied");
return nextMoveIo(board, letters);
}
);
}
public static void playIo(GameState gameState)
{
Console.WriteLine($"Turn: {gameState.WhoseTurn}");
Console.Write(render(gameState.Board));
Console.WriteLine();
var newBoard = nextMoveIo(gameState.Board, gameState.WhoseTurn);
Console.WriteLine();
switch (outcome(newBoard))
{
case Winner winner:
Console.WriteLine($"{winner.Letters} wins!!");
Console.WriteLine(render(newBoard));
break;
case Draw _:
Console.WriteLine("It is a draw!");
break;
case NoneYet _:
playIo(GameState(newBoard, otherPlayer(gameState.WhoseTurn)));
break;
}
}
}
}
}
using static TicTacToe.Schema.Ops;
using static TicTacToe.Schema.Constructors;
using System;
namespace TicTacToe
{
class Program
{
static void Main(string[] args)
{
playIo(initialGameState());
Console.ReadLine();
}
}
}
using System.Collections;
using System.Collections.Generic;
using LanguageExt;
namespace TicTacToe
{
namespace Schema
{
public enum Letters
{
X,
O
}
public abstract class Value { }
public class Unspecified : Value { }
public class Letter : Value
{
public Letters Value { get; }
public Letter(Letters value) => Value = value;
}
public abstract class OneThroughThree
{
public static readonly One One = new One();
public static readonly Two Two = new Two();
public static readonly Three Three = new Three();
public static readonly OneThroughThree[] Values = new OneThroughThree[] { One, Two, Three };
protected OneThroughThree() { }
}
public class One : OneThroughThree { }
public class Two : OneThroughThree { }
public class Three : OneThroughThree { }
public sealed class Position : Record<Position>
{
public OneThroughThree Column { get; }
public OneThroughThree Row { get; }
public Position(OneThroughThree column, OneThroughThree row) =>
(Column, Row) = (column, row);
public void Deconstruct(out OneThroughThree column, out OneThroughThree row) =>
(column, row) = (Column, Row);
}
public sealed class Move : Record<Move>
{
public Position At { get; }
public Letter Place { get; }
public Move(Position at, Letter place) => (At, Place) = (at, place);
}
public sealed class Row : NewType<Row, (Value, Value, Value)>, IEnumerable<Value>
{
public Row((Value, Value, Value) values) : base(values) { }
public void Deconstruct(out Value v1, out Value v2, out Value v3) => (v1, v2, v3) = Value;
public IEnumerator<Value> GetEnumerator()
{
yield return Value.Item1;
yield return Value.Item2;
yield return Value.Item3;
}
public Row With(Value v1 = null, Value v2 = null, Value v3 = null) =>
New((v1 ?? Value.Item1, v2 ?? Value.Item2, v3 ?? Value.Item3));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public sealed class Board : Record<Board>, IEnumerable<Row>
{
public Row Row1 { get; }
public Row Row2 { get; }
public Row Row3 { get; }
public Board((Row, Row, Row) rows) =>
(Row1, Row2, Row3) = rows;
public void Deconstruct(out Row r1, out Row r2, out Row r3) => (r1, r2, r3) = (Row1, Row2, Row3);
public IEnumerator<Row> GetEnumerator()
{
yield return Row1;
yield return Row2;
yield return Row3;
}
public Board With(Row r1 = null, Row r2 = null, Row r3 = null)
{
return new Board((r1 ?? Row1, r2 ?? Row2, r3 ?? Row3));
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public abstract class Outcome { }
public sealed class NoneYet : Outcome { }
public sealed class Winner : Outcome
{
public Winner(Letters letters)
{
Letters = Letters;
}
public Letters Letters { get; }
}
sealed class Draw : Outcome { }
public sealed class GameState : Record<GameState>
{
public Board Board { get; }
public Letters WhoseTurn { get; }
public GameState(Board board, Letters whoseTurn)
{
Board = board;
WhoseTurn = whoseTurn;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment