Add game end condition check and simple scoring end screen
This commit is contained in:
parent
a67aa2c0d9
commit
7b172b5f03
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -580,6 +580,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
|
"itertools",
|
||||||
"rand 0.8.0",
|
"rand 0.8.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -588,6 +589,12 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "2.5.1"
|
version = "2.5.1"
|
||||||
@ -902,6 +909,15 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
@ -9,6 +9,7 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
async-std = { version = "1.8.0", features = ["attributes"] }
|
async-std = { version = "1.8.0", features = ["attributes"] }
|
||||||
html-escape = "0.2.6"
|
html-escape = "0.2.6"
|
||||||
|
itertools = "0.10.0"
|
||||||
rand = "0.8.0"
|
rand = "0.8.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.60"
|
serde_json = "1.0.60"
|
||||||
|
45
src/main.rs
45
src/main.rs
@ -1,4 +1,5 @@
|
|||||||
use async_std::{prelude::*, sync::RwLock};
|
use async_std::{prelude::*, sync::RwLock};
|
||||||
|
use itertools::Itertools;
|
||||||
use rand::{seq::SliceRandom, thread_rng};
|
use rand::{seq::SliceRandom, thread_rng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
@ -53,6 +54,9 @@ enum ServerMessage {
|
|||||||
Notification {
|
Notification {
|
||||||
text: String,
|
text: String,
|
||||||
},
|
},
|
||||||
|
GameOver {
|
||||||
|
score: Vec<(usize, usize)>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Serialize)]
|
||||||
@ -130,6 +134,7 @@ impl Player {
|
|||||||
enum GameState {
|
enum GameState {
|
||||||
Setup,
|
Setup,
|
||||||
InProgress,
|
InProgress,
|
||||||
|
Over,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GameSetup {
|
impl Default for GameSetup {
|
||||||
@ -219,10 +224,29 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.end_game();
|
||||||
|
|
||||||
self.active_player += 1;
|
self.active_player += 1;
|
||||||
self.active_player %= self.players.len();
|
self.active_player %= self.players.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the end game condition is reached and finish the game if so,
|
||||||
|
// sending a message to all clients.
|
||||||
|
fn end_game(&mut self) {
|
||||||
|
let provinces = self.supply.iter().any(|(c, n)| *c == "Province" && *n == 0);
|
||||||
|
let supply = self.supply.iter().filter(|(_, n)| *n == 0).count() > 2;
|
||||||
|
|
||||||
|
if supply || provinces {
|
||||||
|
self.state = GameState::Over;
|
||||||
|
}
|
||||||
|
|
||||||
|
for p in self.players.iter_mut() {
|
||||||
|
p.draw_pile.append(&mut p.played_cards);
|
||||||
|
p.draw_pile.append(&mut p.hand);
|
||||||
|
p.draw_pile.append(&mut p.discard_pile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_active_player(&self, player_id: &str) -> bool {
|
fn is_active_player(&self, player_id: &str) -> bool {
|
||||||
match self.players.get(self.active_player) {
|
match self.players.get(self.active_player) {
|
||||||
None => false,
|
None => false,
|
||||||
@ -294,6 +318,27 @@ async fn broadcast_state(game: &Game) {
|
|||||||
|
|
||||||
send_msg(&game, &p, &sm).await;
|
send_msg(&game, &p, &sm).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if game.state == GameState::Over {
|
||||||
|
let sm = ServerMessage::GameOver {
|
||||||
|
score: game
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, p)| {
|
||||||
|
let score = p.draw_pile.iter().fold(0, |acc, card| match card.as_str() {
|
||||||
|
"Province" => acc + 6,
|
||||||
|
"Duchery" => acc + 3,
|
||||||
|
"Estate" => acc + 1,
|
||||||
|
_ => acc,
|
||||||
|
});
|
||||||
|
(i, score)
|
||||||
|
})
|
||||||
|
.sorted_by(|a, b| Ord::cmp(&b.1, &a.1))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
};
|
||||||
|
broadcast(&game, &sm).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_msg(game: &Game, player: &Player, sm: &ServerMessage) {
|
async fn send_msg(game: &Game, player: &Player, sm: &ServerMessage) {
|
||||||
|
135
static/game.html
135
static/game.html
@ -8,8 +8,9 @@
|
|||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#game {
|
#game {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto 300px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pile-counter {
|
.pile-counter {
|
||||||
@ -53,7 +54,7 @@
|
|||||||
|
|
||||||
.player-hand {
|
.player-hand {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
height: 160px;
|
height: 165px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
@ -68,6 +69,8 @@
|
|||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
grid-column-start: 2;
|
grid-column-start: 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
#chat li[data-repeat]::after {
|
#chat li[data-repeat]::after {
|
||||||
@ -109,7 +112,7 @@
|
|||||||
|
|
||||||
.supply-pile::after {
|
.supply-pile::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 5px;
|
bottom: 20px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
color: wheat;
|
color: wheat;
|
||||||
background-color: darkred;
|
background-color: darkred;
|
||||||
@ -211,6 +214,10 @@
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inplay-area img.card {
|
||||||
|
margin-left: -55px;
|
||||||
|
}
|
||||||
|
|
||||||
.turn-status {
|
.turn-status {
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@ -309,6 +316,57 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#preview {
|
||||||
|
grid-column-start: 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
height: 290px;
|
||||||
|
margin-top: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview img {
|
||||||
|
height: 290px;
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal {
|
||||||
|
display: none;
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-column-end: 2;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(1.0, 1.0, 1.0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 600px;
|
||||||
|
height: 300px;
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: lightgray;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid dimgray;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank span:first-child {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script src="/static/mithril.js"></script>
|
<script src="/static/mithril.js"></script>
|
||||||
</head>
|
</head>
|
||||||
@ -316,7 +374,17 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="game"></div>
|
<div id="game"></div>
|
||||||
<script>
|
<script>
|
||||||
function handle_dnd(data) {
|
var mouseenter = function(ev) {
|
||||||
|
var img = ev.target.src;
|
||||||
|
document.querySelector("#preview img").src = img;
|
||||||
|
document.querySelector("#preview img").style.visibility = "visible";
|
||||||
|
}
|
||||||
|
|
||||||
|
var mouseleave = function(ev) {
|
||||||
|
document.querySelector("#preview img").style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_dnd(data) {
|
||||||
if (data.source == "Hand" && data.dest == "InPlay") {
|
if (data.source == "Hand" && data.dest == "InPlay") {
|
||||||
var msg = { type: "PlayCard", name: "", index: data.index};
|
var msg = { type: "PlayCard", name: "", index: data.index};
|
||||||
webSocket.send(JSON.stringify(msg));
|
webSocket.send(JSON.stringify(msg));
|
||||||
@ -348,7 +416,7 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".chat-window",
|
return [m(".chat-window",
|
||||||
m("h4", "Players"),
|
m("h4", "Players"),
|
||||||
m("ul", vnode.attrs.players.map(function(p) {
|
m("ul", vnode.attrs.players.map(function(p) {
|
||||||
return m("li", p.name);
|
return m("li", p.name);
|
||||||
@ -359,8 +427,10 @@
|
|||||||
id: "chat_input",
|
id: "chat_input",
|
||||||
placeholder: "Type to chat...",
|
placeholder: "Type to chat...",
|
||||||
onkeyup: keyup
|
onkeyup: keyup
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
|
m("#preview", m("img")),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -376,7 +446,7 @@
|
|||||||
ev.dataTransfer.setData("text", JSON.stringify(data));
|
ev.dataTransfer.setData("text", JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".supply-pile",
|
return m(".supply-pile",
|
||||||
{
|
{
|
||||||
@ -390,6 +460,8 @@
|
|||||||
class: "card",
|
class: "card",
|
||||||
src: "/static/images/cards/" + vnode.attrs.name.toLowerCase() + ".jpg",
|
src: "/static/images/cards/" + vnode.attrs.name.toLowerCase() + ".jpg",
|
||||||
draggable: false,
|
draggable: false,
|
||||||
|
onmouseenter: mouseenter,
|
||||||
|
onmouseleave: mouseleave,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -693,19 +765,34 @@
|
|||||||
m("h4", "Basic cards"),
|
m("h4", "Basic cards"),
|
||||||
m(".basic-cards",
|
m(".basic-cards",
|
||||||
vnode.attrs.basic_cards.map(function(card) {
|
vnode.attrs.basic_cards.map(function(card) {
|
||||||
return m("img", {class: "card", src: "/static/images/cards/" + card.toLowerCase() + ".jpg"})
|
return m("img", {
|
||||||
|
class: "card",
|
||||||
|
src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
|
||||||
|
onmouseenter: mouseenter,
|
||||||
|
onmouseleave: mouseleave,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
m("h4", "Kingdom Cards"),
|
m("h4", "Kingdom Cards"),
|
||||||
m(".kingdom-cards",
|
m(".kingdom-cards",
|
||||||
vnode.attrs.kingdom_cards.map(function(card) {
|
vnode.attrs.kingdom_cards.map(function(card) {
|
||||||
return m("img", {class: "card", src: "/static/images/cards/" + card.toLowerCase() + ".jpg"})
|
return m("img", {
|
||||||
|
class: "card",
|
||||||
|
src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
|
||||||
|
onmouseenter: mouseenter,
|
||||||
|
onmouseleave: mouseleave,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
m("h4", "Starting Deck"),
|
m("h4", "Starting Deck"),
|
||||||
m(".start-deck",
|
m(".start-deck",
|
||||||
vnode.attrs.starting_deck.map(function(card) {
|
vnode.attrs.starting_deck.map(function(card) {
|
||||||
return m("img", {class: "card", src: "/static/images/cards/" + card.toLowerCase() + ".jpg"})
|
return m("img", {
|
||||||
|
class: "card",
|
||||||
|
src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
|
||||||
|
onmouseenter: mouseenter,
|
||||||
|
onmouseleave: mouseleave,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
m("button", {onclick: start_click}, "Start Game")
|
m("button", {onclick: start_click}, "Start Game")
|
||||||
@ -714,10 +801,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function EndScreen(initialVnode) {
|
||||||
|
return {
|
||||||
|
view: function(vnode) {
|
||||||
|
return m(".modal-content",
|
||||||
|
m("h1", "Score"),
|
||||||
|
gameover_state.map(function(rank) {
|
||||||
|
return m(".rank",
|
||||||
|
m("span", game_state.players[rank[0]].name),
|
||||||
|
m("span", rank[1])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var game_state;
|
var game_state;
|
||||||
var setup_state;
|
var setup_state;
|
||||||
var my_player_id = 0;
|
var my_player_id = 0;
|
||||||
var webSocket;
|
var webSocket;
|
||||||
|
var gameover_state;
|
||||||
|
|
||||||
function App(initialVnode) {
|
function App(initialVnode) {
|
||||||
if (document.location.protocol == "https:") {
|
if (document.location.protocol == "https:") {
|
||||||
@ -779,6 +883,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var handle_gameover = function(msg) {
|
||||||
|
gameover_state = msg.score;
|
||||||
|
var modal = document.querySelector("#modal");
|
||||||
|
modal.style.display = "block";
|
||||||
|
m.mount(modal, EndScreen);
|
||||||
|
}
|
||||||
|
|
||||||
webSocket.onopen = function(event) {
|
webSocket.onopen = function(event) {
|
||||||
console.log("ws open");
|
console.log("ws open");
|
||||||
@ -815,6 +925,8 @@
|
|||||||
handle_game_state(msg);
|
handle_game_state(msg);
|
||||||
} else if (msg.type == "GameSetup") {
|
} else if (msg.type == "GameSetup") {
|
||||||
handle_setup(msg.setup);
|
handle_setup(msg.setup);
|
||||||
|
} else if (msg.type == "GameOver") {
|
||||||
|
handle_gameover(msg);
|
||||||
} else if (msg.type == "PlayerHand") {
|
} else if (msg.type == "PlayerHand") {
|
||||||
game_state.player.hand = msg.hand;
|
game_state.player.hand = msg.hand;
|
||||||
} else if (msg.type == "PlayerId") {
|
} else if (msg.type == "PlayerId") {
|
||||||
@ -832,6 +944,7 @@
|
|||||||
return [
|
return [
|
||||||
m(SetupScreen, setup_state),
|
m(SetupScreen, setup_state),
|
||||||
m(GameScreen, game_state),
|
m(GameScreen, game_state),
|
||||||
|
m("#modal"),
|
||||||
m(Chat, chat_state)
|
m(Chat, chat_state)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user