Created
August 28, 2018 06:10
-
-
Save michael-wolfenden/b91bff68adef902063ffe7a4f86c63a7 to your computer and use it in GitHub Desktop.
Port of F# Tic-Tac-Toe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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