Skip to content

Commit

Permalink
Building frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
epifab committed Mar 3, 2024
1 parent 64aa560 commit f6c8646
Show file tree
Hide file tree
Showing 21 changed files with 220 additions and 491 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ jobs:
node-version: 18
- name: Build
run: |
sbt test sdk/fullLinkJS
sbt sdk/fullLinkJS
cp modules/sdk/target/scala-3.3.1/sdk-opt/main.js modules/sdk/src/main/typescript/index.js
cd modules/sdk/src/main/typescript
npm install
find model -name "*-ti.ts" -type f -delete
`npm bin`/ts-interface-builder model/*.ts
npm link
cd ../../../../../frontend
npm install
Expand Down
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,6 @@ lazy val backend = (project in file("modules/backend"))
fork := true
)

lazy val root = (project in file(".")).aggregate(domain.jvm, backend)
lazy val root = (project in file("."))
.aggregate(domain.jvm, backend)
.settings(name := "bastoni")
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

# production
/build
/dist

# misc
.DS_Store
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
html, body {
margin: 0 !important;
padding: 0 !important;
overflow: hidden;
}

body {
background: black;
color: white;
font-family: 'Open Sans', sans-serif;
}
Expand Down
86 changes: 22 additions & 64 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,39 @@
import React, {useEffect, useState} from 'react';
import {useEffect, useState} from 'react';
import './App.css';
import {fetchAuthToken} from "bastoni/authClient";
import {GameClientBuilder} from "bastoni/gameClient";
import {authenticateMessage, connectMessage, joinTableMessage, OutboxMessage} from "bastoni/model/outboxMessage";
import Card from "./components/Card";
import {OutboxMessage, pongMessage} from "bastoni/model/outboxMessage";
import {GameRoom} from "./components/GameRoom";
import {GameType} from "bastoni/model/gameType"
import {CardStyle} from "./view/CardStyle";
import {Stage} from "@pixi/react";
import {playAgainstComputer} from "bastoni"
import {Room, RoomId} from "bastoni/model/room"
import {CardSuit, CardRank} from "bastoni/model/card"
import {Room} from "bastoni/model/room"
import {InboxMessage} from "bastoni/model/inboxMessage"

async function connect() {
const roomId: RoomId = 'ab24cf47-505a-4794-85a6-2866749eb4f5';
const playerName = 'John Doe';
const authToken = await fetchAuthToken(playerName, 'localhost:9000', false);
return new GameClientBuilder()
.onReady((client) => client.send(authenticateMessage(authToken)))
.onAuthenticated((user, client) => client.send(connectMessage))
.onConnected((room, client) => client.send(joinTableMessage))
.onPlayerJoinedTable((event) => console.log(`${event.user.name} joined the room`))
.onPlayerLeftTable((event) => console.log(`${event.user.name} left the room`))
.build(roomId);
}
// async function connect() {
// const roomId: RoomId = 'ab24cf47-505a-4794-85a6-2866749eb4f5';
// const playerName = 'John Doe';
// const authToken = await fetchAuthToken(playerName, 'localhost:9000', false);
// return new GameClientBuilder()
// .onReady((client) => client.send(authenticateMessage(authToken)))
// .onAuthenticated((user, client) => client.send(connectMessage))
// .onConnected((room, client) => client.send(joinTableMessage))
// .onPlayerJoinedTable((event) => console.log(`${event.user.name} joined the room`))
// .onPlayerLeftTable((event) => console.log(`${event.user.name} left the room`))
// .build(roomId);
// }


export function App() {
const [room, setRoom] = useState<Room | undefined>(undefined)
const [controller, setController] = useState<(message: OutboxMessage) => void>((message: OutboxMessage) => {
})

useEffect(() => {
const cleanup: () => void = playAgainstComputer(
'Me',
GameType.Briscola,
(message: InboxMessage, room: Room) => {
(message: InboxMessage, room?: Room) => {
console.log(message)
console.log(room)
setRoom(room)
},
(sendMessage: (message: OutboxMessage) => void) => setController(sendMessage)
(sendMessage: (message: OutboxMessage) => void) => sendMessage(pongMessage)
)
console.info("Game loaded")
return () => {
Expand All @@ -56,45 +50,9 @@ export function App() {
// })

return (
<Stage options={{antialias: true, autoDensity: false, resolution: 1}} width={window.innerWidth}
height={window.innerHeight}>
<Card
card={{
rank: CardRank.Tre,
suit: CardSuit.Bastoni,
ref: 3
}}
width={200}
topLeft={{x: 0, y: 0}}
style={CardStyle.Napoletane}
/>
<Card
card={{
rank: CardRank.Asso,
suit: CardSuit.Spade,
ref: 3
}}
width={200}
topLeft={{x: 900, y: 500}}
style={CardStyle.Napoletane}
rotation={{deg: 2}}
/>
<Card
card={{ref: 2}}
width={200}
topLeft={{x: 100, y: 200}}
style={CardStyle.Napoletane}
/>
</Stage>
// <div className="App">
// <header className="App-header">
// <img src={logo} className="App-logo" alt="logo"/>
// <p>
// Edit <code>src/App.tsx</code> and save to reload.
// </p>
//
// </header>
// </div>
<>
{room ? <GameRoom room={room} /> : <p>loading</p>}
</>
);
}

Expand Down
9 changes: 5 additions & 4 deletions frontend/src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import {Sprite} from "@pixi/react";
import CardLayout from "../view/CardLayout";
import {isVisible} from "bastoni/model/card";
import napoletane from "../view/cards/napoletane/resources";
import piacentine from "../view/cards/piacentine/resources";
import napoletane from "../view/cards/napoletane";
import piacentine from "../view/cards/piacentine";
import retro from "../view/cards/retro.tsx";
import {CardStyle} from "../view/CardStyle";
import {useEffect, useState} from "react";
import {Texture} from "pixi.js";
// import {DropShadowFilter} from '@pixi/filter-drop-shadow';

export default function Card(layout: CardLayout) {

const [card, setCard] = useState<Texture>(null);
const [card, setCard] = useState<Texture | undefined>(undefined);
const svgs = layout.style === CardStyle.Piacentine ? piacentine : napoletane;

useEffect(() => {
async function loadCard() {
const uri = isVisible(layout.card) ? svgs[layout.card.suit][layout.card.rank] : './cards/retro.svg';
const uri = isVisible(layout.card) ? svgs[layout.card.suit][layout.card.rank] : retro;
setCard(await Texture.fromURL(uri));
}

Expand Down
5 changes: 0 additions & 5 deletions frontend/src/components/Deck.tsx

This file was deleted.

52 changes: 52 additions & 0 deletions frontend/src/components/GameRoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {Room} from "bastoni/model/room"
import {CardSuit, CardRank} from "bastoni/model/card"
import {Stage, Text} from "@pixi/react";
import Card from "./Card.tsx";
import {CardStyle} from "../view/CardStyle.ts";
import {TextStyle} from "pixi.js";

interface RoomProps {
room: Room
}

export function GameRoom({room}: RoomProps) {
return (
<Stage options={{antialias: true, autoDensity: false, resolution: 1}}
width={window.innerWidth}
height={window.innerHeight}
>
<Text
text={room.players[room.me]?.name}
style={new TextStyle({fill: 0xFFFFFF, fontSize: 40})}
/>

<Card
card={{
rank: CardRank.Tre,
suit: CardSuit.Bastoni,
ref: 3
}}
width={200}
topLeft={{x: 0, y: 0}}
style={CardStyle.Napoletane}
/>
<Card
card={{
rank: CardRank.Asso,
suit: CardSuit.Spade,
ref: 3
}}
width={200}
topLeft={{x: 900, y: 500}}
style={CardStyle.Napoletane}
rotation={{deg: 2}}
/>
<Card
card={{ref: 2}}
width={200}
topLeft={{x: 100, y: 200}}
style={CardStyle.Napoletane}
/>
</Stage>
)
}
2 changes: 1 addition & 1 deletion frontend/src/view/RoomLayout.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Room} from "bastoni/model/room";
import CardLayout from "./CardLayout";
import {CardStyle} from "./CardStyle";
import {isActing, PlayerState} from "bastoni/model/player";
import {isActing} from "bastoni/model/player";

export default class RoomLayout {
deck: CardLayout[]
Expand Down
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions frontend/src/view/cards/retro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const retro = './cards/retro.svg'

export default retro
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import bastoni.domain.model.*
import bastoni.domain.model.Command.*
import bastoni.domain.model.Event.*
import bastoni.domain.view.{FromPlayer, ToPlayer}
import bastoni.domain.view.FromPlayer.GameCommand
import cats.effect.{Resource, Sync}
import cats.effect.syntax.all.*
import cats.effect.Sync
import cats.syntax.all.*
import cats.Monad
import org.typelevel.log4cats.Logger
Expand All @@ -20,7 +18,21 @@ trait GamePublisher[F[_]]:
def publish(me: User, roomId: RoomId)(input: fs2.Stream[F, FromPlayer]): fs2.Stream[F, Unit]
def publish1(me: User, roomId: RoomId)(input: FromPlayer): F[Unit]

trait GameController[F[_]] extends GameSubscriber[F] with GamePublisher[F]
trait GameController[F[_]] extends GameSubscriber[F] with GamePublisher[F]:
def connectPlayer(me: User, roomId: RoomId): fs2.Stream[F, (ToPlayer, Option[RoomPlayerView])] =
subscribe(me, roomId)
.takeThrough {
case ToPlayer.Disconnected(_) => false
case _ => true
}
.zipWithScan1(Option.empty[RoomPlayerView]) {
case (_, ToPlayer.Connected(room)) => Some(room)
case (_, ToPlayer.Disconnected(_)) => None
case (room, ToPlayer.Request(request)) => room.map(_.withRequest(request))
case (room, ToPlayer.GameEvent(event)) => room.map(_.update(event))
case (room, ToPlayer.Authenticated(_)) => room
case (room, ToPlayer.Ping) => room
}

object GameController:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class IntegrationSpec extends AsyncIOFreeSpec:
delayDuration =
if realSpeed then Delay.default
else {
case Delay.ActionTimeout => 100.millis
case Delay.ActionTimeout => 2.hours
case _ => 2.millis
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package bastoni.domain.logic

import bastoni.domain.AsyncIOFreeSpec
import bastoni.domain.model.Event.{PlayerJoinedTable, PlayerLeftTable}
import bastoni.domain.model.{RoomId, User, UserId}
import bastoni.domain.view.{FromPlayer, ToPlayer}
import cats.effect.IO

class PlayerConnectionSpec extends AsyncIOFreeSpec:
private val me = User(UserId.newId, "me")
private val roomId = RoomId.newId

"A player can connect and join a table" in {
val input = fs2.Stream(
FromPlayer.Connect,
FromPlayer.JoinTable,
FromPlayer.LeaveTable
)

Services
.inMemory[IO]
.use { case (controller, run) =>
controller
.connectPlayer(me, roomId)
.take(3)
.concurrently(controller.publish(me, roomId)(input))
.concurrently(run)
.compile
.toList
}
.asserting {
case (msg1, room1) :: (msg2, room2) :: (msg3, room3) :: Nil =>
msg1 shouldBe a[ToPlayer.Connected]
msg2 match
case ToPlayer.GameEvent(PlayerJoinedTable(joiner, _)) =>
joiner shouldBe me
case _ =>
fail("Unexpected message #2")
msg3 match
case ToPlayer.GameEvent(PlayerLeftTable(leaver, _)) =>
leaver shouldBe me
case _ =>
fail("Unexpected message #3")

room1.fold(fail("Room not returned"))(_.players.get(me.id) shouldBe None)
room2.fold(fail("Room not returned"))(_.players.get(me.id) shouldBe Some(me))
room2.fold(fail("Room not returned"))(_.seatFor(me) shouldBe defined)
room3.fold(fail("Room not returned"))(_.players.get(me.id) shouldBe Some(me))
room3.fold(fail("Room not returned"))(_.seatFor(me) should not be defined)

case msgs => fail(s"Unexpected number of messages produced: ${msgs.length}")
}
}
end PlayerConnectionSpec
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package bastoni.domain.model

import bastoni.domain.model.Event.PlayerJoinedTable
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers

class RoomViewSpec extends AnyFreeSpec with Matchers:

val me: User = User(UserId.newId, "me")
val emptyRoom: RoomPlayerView = RoomPlayerView(
me.id,
List(
EmptySeat(1, Nil, Nil),
EmptySeat(2, Nil, Nil)
),
Nil,
Nil,
None,
None,
Map.empty
)

"Player joining an empty table" in {
val room: RoomPlayerView = emptyRoom.update(PlayerJoinedTable(me, 1))
room.players.get(me.id) shouldBe Some(me)
}
Loading

0 comments on commit f6c8646

Please sign in to comment.