|
1 | 1 | package com.github.daenyth.opentak.protocol |
2 | 2 |
|
3 | | -import com.github.daenyth.taklib.{GameEndResult, Stone} |
| 3 | +import com.github.daenyth.opentak.protocol.Playtak.{GameNumber, RoomName, Username} |
| 4 | +import com.github.daenyth.taklib.Implicits.RichParsing |
| 5 | +import com.github.daenyth.taklib._ |
| 6 | + |
| 7 | +import scala.util.parsing.combinator.RegexParsers |
4 | 8 |
|
5 | 9 | object PlaytakCodec { |
6 | 10 |
|
7 | | - object Incoming { |
8 | | - def decode(incoming: String): Either[String, Playtak.Incoming] = ??? |
| 11 | + object Incoming extends RegexParsers with RichParsing { |
| 12 | + def decode(input: String): Either[String, Playtak.Incoming] = |
| 13 | + parseEither(incoming, input).toEither |
| 14 | + |
| 15 | + import Playtak.Incoming._ |
| 16 | + |
| 17 | + val client: Parser[Client] = "Client" ~ "([A-Za-z-.0-9]{4,15})".r ^^ { |
| 18 | + case _ ~ s => |
| 19 | + Client(s) |
| 20 | + } |
| 21 | + |
| 22 | + val msg: Parser[String] = """[^\n\r]{1,256}""".r |
| 23 | + val num: Parser[Int] = """\d""".r ^^ { _.toInt } |
| 24 | + val nums: Parser[Int] = """\d+""".r ^^ { _.toInt } |
| 25 | + val username: Parser[Username] = "[a-zA-Z][a-zA-Z0-9_]{3,15}".r ^^ Username.apply |
| 26 | + val email: Parser[String] = "[A-Za-z.0-9_+!#$%&'*^?=-]{1,30}@[A-Za-z.0-9-]{3,30}".r |
| 27 | + val password: Parser[String] = """[^\n\r\s]{6,50}""".r |
| 28 | + val gameNumber: Parser[GameNumber] = "Game#" ~ nums ^^ { case _ ~ n => GameNumber(n) } |
| 29 | + val boardIndex: Parser[BoardIndex] = "([ABCDEFGH])([12345678])".r ^^ { str => |
| 30 | + val fileChr = str.charAt(0) |
| 31 | + val file = "_ABCDEFGH".toCharArray.indexOf(fileChr) |
| 32 | + val rank = str.charAt(1).toString.toInt |
| 33 | + BoardIndex(file, rank) |
| 34 | + } |
| 35 | + val roomName: Parser[RoomName] = """[^\n\r\\s]{4,15}""".r ^^ RoomName |
| 36 | + def simpleGameMessage[A](str: String, gameMsg: GameNumber => A): Parser[A] = |
| 37 | + gameNumber <~ str ^^ gameMsg |
| 38 | + |
| 39 | + val register: Parser[Register] = "Register" ~> username ~ email ^^ { |
| 40 | + case username ~ email => |
| 41 | + Register(username, email) |
| 42 | + } |
| 43 | + val userLogin: Parser[UserLogin] = "Login" ~> username ~ password ^^ { |
| 44 | + case username ~ password => |
| 45 | + UserLogin(username, password) |
| 46 | + } |
| 47 | + val guestLogin: Parser[GuestLogin.type] = "Login Guest" ^^^ GuestLogin |
| 48 | + val logout: Parser[Logout.type] = "^quit$".r ^^^ Logout |
| 49 | + val seek: Parser[Seek] = "Seek" ~> num ~ nums ~ nums ~ "[WB]?".r ^^ { |
| 50 | + case size ~ time ~ increment ~ color => |
| 51 | + val asPlayer = color match { |
| 52 | + case "W" => Some(White) |
| 53 | + case "B" => Some(Black) |
| 54 | + case _ => None |
| 55 | + } |
| 56 | + Seek(size, time, increment, asPlayer) |
| 57 | + } |
| 58 | + val accept: Parser[Accept] = "Accept" ~> nums ^^ { n => |
| 59 | + Accept(GameNumber(n)) |
| 60 | + } |
| 61 | + val place: Parser[Place] = gameNumber ~ " P " ~ boardIndex ~ "[CW]?" ^^ { |
| 62 | + case gameNumber ~ _ ~ idx ~ stoneType => |
| 63 | + val playStone = stoneType match { |
| 64 | + case "C" => PlayCapstone(idx) |
| 65 | + case "W" => PlayStanding(idx) |
| 66 | + case _ => PlayFlat(idx) |
| 67 | + } |
| 68 | + Place(gameNumber, playStone) |
| 69 | + } |
| 70 | + val move: Parser[Move] = gameNumber ~ " M " ~ boardIndex ~ boardIndex ~ rep(num) ^^ { |
| 71 | + case n ~ _ ~ start ~ end ~ drops => Move(n, start, end, drops.toVector) |
| 72 | + } |
| 73 | + val offerDraw: Parser[OfferDraw] = simpleGameMessage("OfferDraw", OfferDraw) |
| 74 | + val rescindDrawOffer: Parser[RescindDrawOffer] = |
| 75 | + simpleGameMessage("RemoveDraw", RescindDrawOffer) |
| 76 | + val resign: Parser[Resign] = simpleGameMessage("Resign", Resign) |
| 77 | + val show: Parser[Show] = simpleGameMessage("Show", Show) |
| 78 | + val requestUndo: Parser[RequestUndo] = simpleGameMessage("RequestUndo", RequestUndo) |
| 79 | + val rescindUndoRequest: Parser[RescindUndoRequest] = |
| 80 | + simpleGameMessage("RemoveUndo", RescindUndoRequest) |
| 81 | + val listSeeks: Parser[ListSeeks.type] = "^List$".r ^^^ ListSeeks |
| 82 | + val listGames: Parser[ListGames.type] = "^GameList$".r ^^^ ListGames |
| 83 | + val subscribe: Parser[Subscribe] = "Observe" ~> nums ^^ { n => |
| 84 | + Subscribe(GameNumber(n)) |
| 85 | + } |
| 86 | + val unsubscribe: Parser[Unsubscribe] = "Unobserve" ~> nums ^^ { n => |
| 87 | + Unsubscribe(GameNumber(n)) |
| 88 | + } |
| 89 | + val shout: Parser[Shout] = "Shout" ~> msg ^^ Shout |
| 90 | + val joinRoom: Parser[JoinRoom] = "JoinRoom" ~> roomName ^^ JoinRoom |
| 91 | + val leaveRoom: Parser[LeaveRoom] = "LeaveRoom" ~> roomName ^^ LeaveRoom |
| 92 | + val shoutRoom: Parser[ShoutRoom] = "ShoutRoom" ~> roomName ~ msg ^^ { |
| 93 | + case room ~ msg => ShoutRoom(room, msg) |
| 94 | + } |
| 95 | + val tell: Parser[Tell] = "Tell" ~> username ~ msg ^^ { case user ~ msg => Tell(user, msg) } |
| 96 | + val ping: Parser[Ping.type] = "^PING$".r ^^^ Ping |
| 97 | + |
| 98 | + val incoming: Parser[Playtak.Incoming] = |
| 99 | + (client | register | userLogin | guestLogin |
| 100 | + | logout | seek | accept | place | move | offerDraw | rescindDrawOffer |
| 101 | + | resign | show | requestUndo | rescindUndoRequest | listSeeks | listGames |
| 102 | + | subscribe | unsubscribe | shout | joinRoom | shoutRoom | leaveRoom | tell | ping) |
| 103 | + |
9 | 104 | } |
10 | 105 |
|
11 | 106 | object Outgoing { |
@@ -65,10 +160,12 @@ object PlaytakCodec { |
65 | 160 | import GameEndResult._ |
66 | 161 | val result = o.result match { |
67 | 162 | case RoadWin(player) => player.fold("0-R", "R-0") |
68 | | - case DoubleRoad => "R-R" // Not actually supported by playtak or default rules, but different result sets can treat it differently. |
| 163 | + case DoubleRoad => |
| 164 | + "R-R" // Not actually supported by playtak or default rules, but different result sets can treat it differently. |
69 | 165 | case FlatWin(player) => player.fold("0-F", "F-0") |
70 | 166 | case Draw => "1/2-1/2" |
71 | | - case WinByResignation(player) => player.fold("0-1", "1-0") // Again not supported by playtak; this is PTN format |
| 167 | + case WinByResignation(player) => |
| 168 | + player.fold("0-1", "1-0") // Again not supported by playtak; this is PTN format |
72 | 169 | } |
73 | 170 | s"Game#${o.gameNumber} Over $result" |
74 | 171 | case DrawOffered(gameNumber) => |
|
0 commit comments