Add prototype for player resolveable events using Cellar
This commit is contained in:
parent
b30cc02306
commit
d6e2543b9c
89
src/main.rs
89
src/main.rs
@ -25,6 +25,7 @@ enum ClientMessage {
|
|||||||
DrawCard,
|
DrawCard,
|
||||||
Discard { index: usize },
|
Discard { index: usize },
|
||||||
TrashHand { index: usize },
|
TrashHand { index: usize },
|
||||||
|
HandCardsChosen { choice: Vec<usize> },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@ -60,6 +61,9 @@ enum ServerMessage {
|
|||||||
GameOver {
|
GameOver {
|
||||||
score: Vec<(usize, u32)>,
|
score: Vec<(usize, u32)>,
|
||||||
},
|
},
|
||||||
|
ResolveRequest {
|
||||||
|
request: ResolveRequest,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Serialize)]
|
||||||
@ -165,11 +169,29 @@ impl Default for GameSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Which player's input is requested to resolve the current effect
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
enum ResolvingPlayer {
|
||||||
|
ActivePlayer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sent by the server to tell the client what information is requested
|
||||||
|
/// from the player to resolve the current effect
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
enum ResolveRequest {
|
||||||
|
ChooseHandCardsToDiscard { player: ResolvingPlayer },
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Effect {
|
enum Effect {
|
||||||
|
/// Trigger effect if another card is played while effect is active
|
||||||
|
/// return true to indicate the event is resolved and should be removed
|
||||||
OnCardPlayed(fn(&mut Game, &Card) -> bool),
|
OnCardPlayed(fn(&mut Game, &Card) -> bool),
|
||||||
|
Resolving(ResolveRequest, ResolvingEffect),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResolvingEffect = fn(&mut Game, &ClientMessage);
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
effects: Vec<Effect>,
|
effects: Vec<Effect>,
|
||||||
players: Vec<Player>,
|
players: Vec<Player>,
|
||||||
@ -180,6 +202,10 @@ pub struct Game {
|
|||||||
supply: Vec<(Card, usize)>,
|
supply: Vec<(Card, usize)>,
|
||||||
trash: Vec<Card>,
|
trash: Vec<Card>,
|
||||||
turn_state: TurnState,
|
turn_state: TurnState,
|
||||||
|
|
||||||
|
/// Any effect from a card that requires further input from players
|
||||||
|
/// and blocks the game until fully resolved.
|
||||||
|
resolving_effect: Option<(ResolveRequest, ResolvingEffect)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
@ -194,6 +220,7 @@ impl Game {
|
|||||||
supply: vec![],
|
supply: vec![],
|
||||||
trash: vec![],
|
trash: vec![],
|
||||||
turn_state: TurnState::default(),
|
turn_state: TurnState::default(),
|
||||||
|
resolving_effect: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +253,23 @@ impl Game {
|
|||||||
cost: 2,
|
cost: 2,
|
||||||
types: vec![CardType::Action(|game| {
|
types: vec![CardType::Action(|game| {
|
||||||
action!(game, 1);
|
action!(game, 1);
|
||||||
|
game.add_effect(Effect::Resolving(
|
||||||
|
ResolveRequest::ChooseHandCardsToDiscard {
|
||||||
|
player: ResolvingPlayer::ActivePlayer,
|
||||||
|
},
|
||||||
|
|game, message| {
|
||||||
|
if let ClientMessage::HandCardsChosen { choice } = message {
|
||||||
|
let mut discarded = 0;
|
||||||
|
for c in choice.iter().sorted_by(|a, b| b.cmp(a)) {
|
||||||
|
game.players[game.active_player].discard(*c);
|
||||||
|
discarded += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
game.players[game.active_player].draw(discarded);
|
||||||
|
game.resolving_effect = None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
})],
|
})],
|
||||||
},
|
},
|
||||||
10,
|
10,
|
||||||
@ -429,6 +473,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut effects = self.effects.clone();
|
let mut effects = self.effects.clone();
|
||||||
|
self.effects.clear();
|
||||||
effects.retain(|effect| {
|
effects.retain(|effect| {
|
||||||
match effect {
|
match effect {
|
||||||
Effect::OnCardPlayed(effect) => {
|
Effect::OnCardPlayed(effect) => {
|
||||||
@ -436,11 +481,13 @@ impl Game {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Effect::Resolving(_, _) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
self.effects = effects;
|
self.effects.append(&mut effects);
|
||||||
|
|
||||||
self.players[player_number].played_cards.push(card);
|
self.players[player_number].played_cards.push(card);
|
||||||
true
|
true
|
||||||
@ -473,7 +520,15 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_effect(&mut self, effect: Effect) {
|
fn add_effect(&mut self, effect: Effect) {
|
||||||
self.effects.push(effect);
|
match effect {
|
||||||
|
Effect::OnCardPlayed(_) => {
|
||||||
|
self.effects.push(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect::Resolving(request, effect) => {
|
||||||
|
self.resolving_effect = Some((request, effect));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,6 +610,20 @@ async fn broadcast_state(game: &Game) {
|
|||||||
};
|
};
|
||||||
broadcast(&game, &sm).await;
|
broadcast(&game, &sm).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some((request, _)) = &game.resolving_effect {
|
||||||
|
match request {
|
||||||
|
ResolveRequest::ChooseHandCardsToDiscard { ref player } => match player {
|
||||||
|
ResolvingPlayer::ActivePlayer => {
|
||||||
|
let p = game.players.get(game.active_player).unwrap();
|
||||||
|
let sm = ServerMessage::ResolveRequest {
|
||||||
|
request: request.clone(),
|
||||||
|
};
|
||||||
|
send_msg(&game, &p, &sm).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_msg(game: &Game, player: &Player, sm: &ServerMessage) {
|
async fn send_msg(game: &Game, player: &Player, sm: &ServerMessage) {
|
||||||
@ -847,6 +916,22 @@ async fn main() -> Result<(), std::io::Error> {
|
|||||||
.await;
|
.await;
|
||||||
broadcast_state(&game).await;
|
broadcast_state(&game).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClientMessage::HandCardsChosen { .. } => {
|
||||||
|
let mut games = req.state().games.write().await;
|
||||||
|
let game = games.get_mut(&game_id).unwrap();
|
||||||
|
|
||||||
|
if player_number == game.active_player {
|
||||||
|
match game.resolving_effect {
|
||||||
|
Some((_, ref effect)) => {
|
||||||
|
effect(game, &msg);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast_state(&game).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
static/audio/chipsStack1.ogg
(Stored with Git LFS)
Normal file
BIN
static/audio/chipsStack1.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -81,6 +81,11 @@ img.card:hover {
|
|||||||
transition: transform 0.1s ease-in;
|
transition: transform 0.1s ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-hand img.card.selected {
|
||||||
|
transform: translate(0px, -40px) scale(1.1) !important;
|
||||||
|
box-shadow: 0 0 10px lime;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-window {
|
.chat-window {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
@ -411,13 +416,71 @@ img.card:hover {
|
|||||||
width: 120px;
|
width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
position: absolute;
|
||||||
|
width: 500px;
|
||||||
|
border: 1px solid dimgray;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 200;
|
||||||
|
background-color: rgba(1.0, 1.0, 1.0, 0.9);
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
box-shadow: 0 0 20px black;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
color: white;
|
||||||
|
visibility: hidden;
|
||||||
|
grid-template-rows: 5fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog img {
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-row-end: 3;
|
||||||
|
margin: 5px;
|
||||||
|
width: 180px;
|
||||||
|
height: 288px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog button {
|
||||||
|
grid-row-start: 2;
|
||||||
|
grid-column-start: 2;
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="/static/mithril.js"></script>
|
<script src="/static/mithril.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="dialog" class="dialog">
|
||||||
|
<img class="card" style="margin: 5px; width:180px; height:288px;" src="/static/images/cards/cellar.jpg">
|
||||||
|
<p style="margin: 20px;">Choose any number of cards to discard.</p>
|
||||||
|
<button onclick="dialog_confirm()">Confirm</button>
|
||||||
|
</div>
|
||||||
<div id="game"></div>
|
<div id="game"></div>
|
||||||
<script>
|
<script>
|
||||||
|
var turnStartSound = new Audio("/static/audio/chipsStack1.ogg");
|
||||||
|
|
||||||
|
var resolve_request;
|
||||||
|
var enable_hand_selection = false;
|
||||||
|
|
||||||
|
var toggle_hand_selection = function(index) {
|
||||||
|
document.querySelectorAll(".player-hand img.card")[index].classList.toggle("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
function dialog_confirm(ev) {
|
||||||
|
var selected = [];
|
||||||
|
|
||||||
|
document.querySelectorAll(".player-hand img.card.selected").forEach((c) => {
|
||||||
|
selected.push(parseInt(c.dataset.index));
|
||||||
|
c.classList.remove("selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
var msg = { type: "HandCardsChosen", choice: selected };
|
||||||
|
webSocket.send(JSON.stringify(msg));
|
||||||
|
enable_hand_selection = false;
|
||||||
|
document.querySelector("#dialog").style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
var mouseenter = function(ev) {
|
var mouseenter = function(ev) {
|
||||||
var img = ev.target.src;
|
var img = ev.target.src;
|
||||||
document.querySelector("#preview img").src = img;
|
document.querySelector("#preview img").src = img;
|
||||||
@ -651,6 +714,15 @@ img.card:hover {
|
|||||||
handle_dnd(data);
|
handle_dnd(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var click = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log("hand card click", e.target.dataset.index);
|
||||||
|
if (enable_hand_selection) {
|
||||||
|
toggle_hand_selection(parseInt(e.target.dataset.index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".player-hand",
|
return m(".player-hand",
|
||||||
@ -667,6 +739,7 @@ img.card:hover {
|
|||||||
"data-index": i,
|
"data-index": i,
|
||||||
onmouseenter: mouseenter,
|
onmouseenter: mouseenter,
|
||||||
onmouseleave: mouseleave,
|
onmouseleave: mouseleave,
|
||||||
|
onclick: click,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -927,6 +1000,7 @@ img.card:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var handle_game_state = function(state) {
|
var handle_game_state = function(state) {
|
||||||
|
var last_player = game_state.active_player;
|
||||||
game_state = {
|
game_state = {
|
||||||
...game_state,
|
...game_state,
|
||||||
...state,
|
...state,
|
||||||
@ -940,6 +1014,19 @@ img.card:hover {
|
|||||||
setup_state.active = false;
|
setup_state.active = false;
|
||||||
game_state.active = true;
|
game_state.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (last_player != game_state.active_player) {
|
||||||
|
if (game_state.active_player == my_player_id) {
|
||||||
|
turnStartSound.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle_resolve_request = function(request) {
|
||||||
|
var dlg = document.querySelector("#dialog");
|
||||||
|
dlg.style.visibility = "visible";
|
||||||
|
|
||||||
|
enable_hand_selection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var handle_gameover = function(msg) {
|
var handle_gameover = function(msg) {
|
||||||
@ -951,7 +1038,6 @@ img.card:hover {
|
|||||||
|
|
||||||
webSocket.onopen = function(event) {
|
webSocket.onopen = function(event) {
|
||||||
console.log("ws open");
|
console.log("ws open");
|
||||||
//webSocket.send("HALLO");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
webSocket.onmessage = function(event) {
|
webSocket.onmessage = function(event) {
|
||||||
@ -990,6 +1076,8 @@ img.card:hover {
|
|||||||
game_state.player.hand = msg.hand;
|
game_state.player.hand = msg.hand;
|
||||||
} else if (msg.type == "PlayerId") {
|
} else if (msg.type == "PlayerId") {
|
||||||
my_player_id = msg.id;
|
my_player_id = msg.id;
|
||||||
|
} else if (msg.type == "ResolveRequest") {
|
||||||
|
handle_resolve_request(msg.request);
|
||||||
} else {
|
} else {
|
||||||
console.log("event?");
|
console.log("event?");
|
||||||
console.log(event.data);
|
console.log(event.data);
|
||||||
|
Loading…
Reference in New Issue
Block a user