Skip to content

Commit 462e895

Browse files
Add contributor arnavsharma990 with interactive Tic-Tac-Toe component
1 parent adf2ca7 commit 462e895

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

src/main/scala/gsoc/contributors/all.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ val allContributors = NonEmptyList.of(
1616
parthmozarkar,
1717
`scala-steward`,
1818
`the-ivii`,
19+
arnavsharma990,
1920
`piyush-t24`,
2021
valencik,
2122
`zayd-r`,
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package gsoc
2+
package contributors
3+
4+
import cats.effect.*
5+
import fs2.concurrent.*
6+
import fs2.dom.HtmlElement
7+
import calico.html.io.{*, given}
8+
import calico.syntax.*
9+
10+
object TicTacToe:
11+
enum Player:
12+
case X, O
13+
def symbol: String = this match
14+
case X => ""
15+
case O => ""
16+
def next: Player = this match
17+
case X => O
18+
case O => X
19+
20+
case class State(
21+
board: Vector[Option[Player]],
22+
turn: Player,
23+
result: Option[String]
24+
)
25+
26+
val fresh: State = State(Vector.fill(9)(None), Player.X, None)
27+
28+
private val lines: List[(Int, Int, Int)] = List(
29+
(0, 1, 2), (3, 4, 5), (6, 7, 8),
30+
(0, 3, 6), (1, 4, 7), (2, 5, 8),
31+
(0, 4, 8), (2, 4, 6)
32+
)
33+
34+
def checkResult(board: Vector[Option[Player]]): Option[String] =
35+
lines
36+
.collectFirst {
37+
case (a, b, c)
38+
if board(a).isDefined && board(a) == board(b) && board(b) == board(c) =>
39+
board(a).fold("")(p => s"${p.symbol} wins!")
40+
}
41+
.orElse(Option.when(board.forall(_.isDefined))("It's a draw!"))
42+
43+
def isWinningCell(board: Vector[Option[Player]], idx: Int): Boolean =
44+
lines.exists { case (a, b, c) =>
45+
List(a, b, c).contains(idx) &&
46+
board(a).isDefined && board(a) == board(b) && board(b) == board(c)
47+
}
48+
49+
val arnavsharma990: Contributor = Contributor("arnavsharma990"):
50+
import TicTacToe.*
51+
52+
SignallingRef[IO].of(fresh).toResource.flatMap { state =>
53+
def play(idx: Int): IO[Unit] =
54+
state.update { s =>
55+
if s.board(idx).isDefined || s.result.isDefined then s
56+
else
57+
val newBoard = s.board.updated(idx, Some(s.turn))
58+
State(newBoard, s.turn.next, checkResult(newBoard))
59+
}
60+
61+
div(
62+
p(
63+
"I am ",
64+
strong("@arnavsharma990"),
65+
" on GitHub. I agree to follow the Typelevel Code of Conduct and Typelevel GSoC AI Policy."
66+
),
67+
p(b("Tic-Tac-Toe — challenge a friend!")),
68+
state
69+
.map(s => s.result.getOrElse(s"${s.turn.symbol}'s turn"))
70+
.changes
71+
.map(msg => p(styleAttr := "font-weight:bold;font-size:1.1em", msg)),
72+
div(
73+
styleAttr := "display:inline-grid;grid-template-columns:repeat(3,64px);gap:4px;margin:8px 0",
74+
(0 until 9).toList.map { idx =>
75+
button(
76+
styleAttr <-- state.map { s =>
77+
val bg =
78+
if s.result.isDefined && isWinningCell(s.board, idx) then "#2ecc71"
79+
else "#ecf0f1"
80+
val clr = s.board(idx) match
81+
case Some(Player.X) => "#e74c3c"
82+
case Some(Player.O) => "#3498db"
83+
case None => "#333"
84+
s"width:64px;height:64px;font-size:1.8em;font-weight:bold;cursor:pointer;background:$bg;color:$clr;border:1px solid #bdc3c7;border-radius:4px;display:flex;align-items:center;justify-content:center"
85+
},
86+
state.map(s => s.board(idx).fold("")(_.symbol)),
87+
onClick --> (_.foreach(_ => play(idx)))
88+
)
89+
}
90+
),
91+
button(
92+
styleAttr := "margin-top:8px;padding:6px 18px;font-size:1em;cursor:pointer",
93+
onClick --> (_.foreach(_ => state.set(fresh))),
94+
"New Game 🔄"
95+
)
96+
)
97+
}

0 commit comments

Comments
 (0)