Skip to content

Commit 10ecb2d

Browse files
authored
Merge pull request #49 from nvshinde/nvshinde-cat-shooter
2 parents 5924af0 + 6024161 commit 10ecb2d

2 files changed

Lines changed: 134 additions & 0 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ val allContributors = NonEmptyList.of(
3333
ap44444,
3434
saifk16,
3535
`alokkumardalei-wq`,
36+
`nvshinde`,
3637
boss6825,
3738
yummy_yums,
3839
`thonkpad`,
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package gsoc
2+
package contributors
3+
4+
import cats.effect.*
5+
import cats.effect.std.Random
6+
import fs2.concurrent.*
7+
import fs2.dom.HtmlElement
8+
import calico.html.io.{*, given}
9+
import calico.syntax.*
10+
11+
import org.scalajs.dom
12+
import scala.concurrent.duration.DurationInt
13+
14+
val nvshinde: Contributor = Contributor("nvshinde"):
15+
import CatShooter.*
16+
for
17+
rng <- Random.scalaUtilRandom[IO].toResource
18+
mousePos <- randCol(rng).toResource
19+
ref <- SignallingRef[IO].of(init(mousePos)).toResource
20+
node <- div(
21+
tabIndex := 0,
22+
styleAttr := "outline:none;",
23+
p(
24+
"I am",
25+
span(
26+
styleAttr := "color:mediumseablue;font-weight:bold",
27+
"@nvshinde"
28+
),
29+
" on Github. I agree to follow the Typelevel CoC and GSoC AI policy."
30+
),
31+
p(
32+
styleAttr := "font-size:13px;color:#888;margin:4px 0;",
33+
"Play the Mice invaders game below. Use arrow keys to move left and right and space to shoot!"
34+
),
35+
onKeyDown --> (_.foreach { e =>
36+
e.key match
37+
case "ArrowLeft" => e.preventDefault *> ref.update(moveLeft)
38+
case "ArrowRight" => e.preventDefault *> ref.update(moveRight)
39+
case " " => e.preventDefault *> shootLaser(ref, rng)
40+
case _ => IO.unit
41+
}),
42+
ref.map(renderSpace),
43+
ref.map { s =>
44+
p(
45+
styleAttr := "margin-top:8px;font-size:14px;",
46+
s"Score: ${s.score}"
47+
)
48+
}
49+
)
50+
yield node
51+
52+
private object CatShooter {
53+
val Size = 20
54+
val CellPx = 20
55+
56+
final case class State(
57+
cat: Int,
58+
mouse: Int,
59+
laserRow: Option[Int],
60+
score: Int
61+
)
62+
63+
def randCol(rng: Random[IO]): IO[Int] =
64+
rng.betweenInt(0, Size)
65+
66+
def init(mouse: Int): State = State(
67+
cat = Size / 2,
68+
mouse = mouse,
69+
laserRow = None,
70+
score = 0
71+
)
72+
73+
def moveLeft(s: State): State =
74+
if s.cat > 0 then s.copy(cat = s.cat - 1) else s
75+
76+
def moveRight(s: State): State =
77+
if s.cat < Size - 1 then s.copy(cat = s.cat + 1) else s
78+
79+
def shootLaser(
80+
ref: SignallingRef[IO, State],
81+
rng: Random[IO]
82+
): IO[Unit] =
83+
fs2
84+
.Stream
85+
.range(1, Size)
86+
.evalMap { row => ref.update(_.copy(laserRow = Some(row))) }
87+
.metered(20.millis)
88+
.compile
89+
.drain >>
90+
ref
91+
.modify { s =>
92+
if s.cat == s.mouse then
93+
val updatedScore = s.copy(score = s.score + 1, laserRow = None)
94+
(updatedScore, true)
95+
else (s.copy(laserRow = None), false)
96+
}
97+
.flatMap {
98+
case true =>
99+
randCol(rng).flatMap(newCol => ref.update(_.copy(mouse = newCol)))
100+
case false => IO.unit
101+
}
102+
103+
def renderSpace(s: State): Resource[IO, HtmlElement[IO]] =
104+
div(
105+
styleAttr := s"display:grid;grid-template-columns:repeat($Size, ${CellPx}px); background:#111;width:${Size * CellPx}px;height:${Size * CellPx}px"
106+
).flatMap { gridEl =>
107+
Resource
108+
.eval(IO {
109+
val raw = gridEl.asInstanceOf[dom.HTMLElement]
110+
111+
raw.innerHTML = ""
112+
for
113+
y <- 0 until Size
114+
x <- 0 until Size
115+
do
116+
val cell = dom.document.createElement("div")
117+
cell.setAttribute(
118+
"style",
119+
s"width:${CellPx}px;height:${CellPx}px;display:flex;align-items:center;justify-content:center;color:white;font-weight:bold;font-size:20px"
120+
)
121+
122+
val content =
123+
if y == 0 && x == s.cat then "C"
124+
else if y == Size - 1 && x == s.mouse then "M"
125+
else if s.laserRow.contains(y) && x == s.cat then "*"
126+
else ""
127+
128+
cell.textContent = content
129+
raw.appendChild(cell)
130+
})
131+
.map(_ => gridEl)
132+
}
133+
}

0 commit comments

Comments
 (0)