Add prototype for player resolveable events using Cellar
This commit is contained in:
		
							
								
								
									
										89
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -25,6 +25,7 @@ enum ClientMessage {
 | 
			
		||||
    DrawCard,
 | 
			
		||||
    Discard { index: usize },
 | 
			
		||||
    TrashHand { index: usize },
 | 
			
		||||
    HandCardsChosen { choice: Vec<usize> },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
@@ -60,6 +61,9 @@ enum ServerMessage {
 | 
			
		||||
    GameOver {
 | 
			
		||||
        score: Vec<(usize, u32)>,
 | 
			
		||||
    },
 | 
			
		||||
    ResolveRequest {
 | 
			
		||||
        request: ResolveRequest,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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)]
 | 
			
		||||
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),
 | 
			
		||||
    Resolving(ResolveRequest, ResolvingEffect),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResolvingEffect = fn(&mut Game, &ClientMessage);
 | 
			
		||||
 | 
			
		||||
pub struct Game {
 | 
			
		||||
    effects: Vec<Effect>,
 | 
			
		||||
    players: Vec<Player>,
 | 
			
		||||
@@ -180,6 +202,10 @@ pub struct Game {
 | 
			
		||||
    supply: Vec<(Card, usize)>,
 | 
			
		||||
    trash: Vec<Card>,
 | 
			
		||||
    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 {
 | 
			
		||||
@@ -194,6 +220,7 @@ impl Game {
 | 
			
		||||
            supply: vec![],
 | 
			
		||||
            trash: vec![],
 | 
			
		||||
            turn_state: TurnState::default(),
 | 
			
		||||
            resolving_effect: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -226,6 +253,23 @@ impl Game {
 | 
			
		||||
                            cost: 2,
 | 
			
		||||
                            types: vec![CardType::Action(|game| {
 | 
			
		||||
                                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,
 | 
			
		||||
@@ -429,6 +473,7 @@ impl Game {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut effects = self.effects.clone();
 | 
			
		||||
        self.effects.clear();
 | 
			
		||||
        effects.retain(|effect| {
 | 
			
		||||
            match effect {
 | 
			
		||||
                Effect::OnCardPlayed(effect) => {
 | 
			
		||||
@@ -436,11 +481,13 @@ impl Game {
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Effect::Resolving(_, _) => {}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            true
 | 
			
		||||
        });
 | 
			
		||||
        self.effects = effects;
 | 
			
		||||
        self.effects.append(&mut effects);
 | 
			
		||||
 | 
			
		||||
        self.players[player_number].played_cards.push(card);
 | 
			
		||||
        true
 | 
			
		||||
@@ -473,7 +520,15 @@ impl Game {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
@@ -847,6 +916,22 @@ async fn main() -> Result<(), std::io::Error> {
 | 
			
		||||
                        .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
									
									LFS
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/audio/chipsStack1.ogg
									
									LFS
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -81,6 +81,11 @@ img.card:hover {
 | 
			
		||||
    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 {
 | 
			
		||||
    border: 1px solid black;
 | 
			
		||||
    width: 300px;
 | 
			
		||||
@@ -411,13 +416,71 @@ img.card:hover {
 | 
			
		||||
    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>
 | 
			
		||||
<script src="/static/mithril.js"></script>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<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>
 | 
			
		||||
    <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 img = ev.target.src;
 | 
			
		||||
             document.querySelector("#preview img").src = img;
 | 
			
		||||
@@ -651,6 +714,15 @@ img.card:hover {
 | 
			
		||||
                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 {
 | 
			
		||||
                view: function(vnode) {
 | 
			
		||||
                    return m(".player-hand",
 | 
			
		||||
@@ -667,6 +739,7 @@ img.card:hover {
 | 
			
		||||
                                "data-index": i,
 | 
			
		||||
                                onmouseenter: mouseenter,
 | 
			
		||||
                                onmouseleave: mouseleave,
 | 
			
		||||
                                onclick: click,
 | 
			
		||||
                            })
 | 
			
		||||
                        })
 | 
			
		||||
                    )
 | 
			
		||||
@@ -927,6 +1000,7 @@ img.card:hover {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var handle_game_state = function(state) {
 | 
			
		||||
                var last_player = game_state.active_player;
 | 
			
		||||
                game_state = {
 | 
			
		||||
                    ...game_state,
 | 
			
		||||
                    ...state,
 | 
			
		||||
@@ -940,6 +1014,19 @@ img.card:hover {
 | 
			
		||||
                    setup_state.active = false;
 | 
			
		||||
                    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) {
 | 
			
		||||
@@ -951,7 +1038,6 @@ img.card:hover {
 | 
			
		||||
   
 | 
			
		||||
            webSocket.onopen = function(event) {
 | 
			
		||||
                console.log("ws open");
 | 
			
		||||
                //webSocket.send("HALLO");
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            webSocket.onmessage = function(event) {
 | 
			
		||||
@@ -990,6 +1076,8 @@ img.card:hover {
 | 
			
		||||
                    game_state.player.hand = msg.hand;
 | 
			
		||||
                } else if (msg.type == "PlayerId") {
 | 
			
		||||
                    my_player_id = msg.id;
 | 
			
		||||
                } else if (msg.type == "ResolveRequest") {
 | 
			
		||||
                    handle_resolve_request(msg.request);
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.log("event?");
 | 
			
		||||
                    console.log(event.data);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user