Initial commit
parent
a3c00e2fcd
commit
fa6755b647
|
@ -0,0 +1,2 @@
|
||||||
|
target/
|
||||||
|
dist/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,10 @@
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = true # link time optimisation
|
||||||
|
codegen-units = 1 # fewer -> more optimisation
|
||||||
|
panic = 'abort'
|
||||||
|
strip = 'symbols'
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["server", "frontend"]
|
||||||
|
resolver = '2'
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "frontend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
enum_glob_use = "deny"
|
||||||
|
pedantic = "deny"
|
||||||
|
nursery = "deny"
|
||||||
|
unwrap_used = "deny"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.7.5"
|
||||||
|
clap = { version = "4.5.11", features = ["derive"] }
|
||||||
|
log = "0.4.22"
|
||||||
|
tokio = { version = "1.39.1", features = ["full"] }
|
||||||
|
tower = "0.4.13"
|
||||||
|
tower-http = { version = "0.5.2", features = ["full"] }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-subscriber = "0.3.18"
|
|
@ -0,0 +1,78 @@
|
||||||
|
pub mod consts;
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub struct Card {
|
||||||
|
value: Value,
|
||||||
|
suit: Suit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Card {
|
||||||
|
pub const fn new(value: Value, suit: Suit) -> Self {
|
||||||
|
Self { value, suit }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for Card {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}{}", self.value, self.suit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum Suit {
|
||||||
|
Clubs,
|
||||||
|
Diamonds,
|
||||||
|
Hearts,
|
||||||
|
Spades,
|
||||||
|
}
|
||||||
|
impl Display for Suit {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f, "{}",
|
||||||
|
match self {
|
||||||
|
Self::Clubs => "♣",
|
||||||
|
Self::Diamonds => "◆",
|
||||||
|
Self::Hearts => "♥",
|
||||||
|
Self::Spades => "♠",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum Value {
|
||||||
|
Ace = 1,
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
Four,
|
||||||
|
Five,
|
||||||
|
Six,
|
||||||
|
Seven,
|
||||||
|
Eight,
|
||||||
|
Nine,
|
||||||
|
Ten,
|
||||||
|
Jack,
|
||||||
|
Queen,
|
||||||
|
King,
|
||||||
|
}
|
||||||
|
impl Display for Value {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f, "{}",
|
||||||
|
match self {
|
||||||
|
Self::Ace => "A",
|
||||||
|
Self::Two => "2",
|
||||||
|
Self::Three => "3",
|
||||||
|
Self::Four => "4",
|
||||||
|
Self::Five => "5",
|
||||||
|
Self::Six => "6",
|
||||||
|
Self::Seven => "7",
|
||||||
|
Self::Eight => "8",
|
||||||
|
Self::Nine => "9",
|
||||||
|
Self::Ten => "10",
|
||||||
|
Self::Jack => "J",
|
||||||
|
Self::Queen => "Q",
|
||||||
|
Self::King => "K",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
use super::{Card, Suit, Value};
|
||||||
|
|
||||||
|
pub const CARDS_PER_PLAYER: usize = 7;
|
||||||
|
|
||||||
|
pub const DECK: [Card; 52] = [
|
||||||
|
Card::new(Value::Ace, Suit::Clubs),
|
||||||
|
Card::new(Value::Two, Suit::Clubs),
|
||||||
|
Card::new(Value::Three, Suit::Clubs),
|
||||||
|
Card::new(Value::Four, Suit::Clubs),
|
||||||
|
Card::new(Value::Five, Suit::Clubs),
|
||||||
|
Card::new(Value::Six, Suit::Clubs),
|
||||||
|
Card::new(Value::Seven, Suit::Clubs),
|
||||||
|
Card::new(Value::Eight, Suit::Clubs),
|
||||||
|
Card::new(Value::Nine, Suit::Clubs),
|
||||||
|
Card::new(Value::Ten, Suit::Clubs),
|
||||||
|
Card::new(Value::Jack, Suit::Clubs),
|
||||||
|
Card::new(Value::Queen, Suit::Clubs),
|
||||||
|
Card::new(Value::King, Suit::Clubs),
|
||||||
|
Card::new(Value::Ace, Suit::Diamonds),
|
||||||
|
Card::new(Value::Two, Suit::Diamonds),
|
||||||
|
Card::new(Value::Three, Suit::Diamonds),
|
||||||
|
Card::new(Value::Four, Suit::Diamonds),
|
||||||
|
Card::new(Value::Five, Suit::Diamonds),
|
||||||
|
Card::new(Value::Six, Suit::Diamonds),
|
||||||
|
Card::new(Value::Seven, Suit::Diamonds),
|
||||||
|
Card::new(Value::Eight, Suit::Diamonds),
|
||||||
|
Card::new(Value::Nine, Suit::Diamonds),
|
||||||
|
Card::new(Value::Ten, Suit::Diamonds),
|
||||||
|
Card::new(Value::Jack, Suit::Diamonds),
|
||||||
|
Card::new(Value::Queen, Suit::Diamonds),
|
||||||
|
Card::new(Value::King, Suit::Diamonds),
|
||||||
|
Card::new(Value::Ace, Suit::Hearts),
|
||||||
|
Card::new(Value::Two, Suit::Hearts),
|
||||||
|
Card::new(Value::Three, Suit::Hearts),
|
||||||
|
Card::new(Value::Four, Suit::Hearts),
|
||||||
|
Card::new(Value::Five, Suit::Hearts),
|
||||||
|
Card::new(Value::Six, Suit::Hearts),
|
||||||
|
Card::new(Value::Seven, Suit::Hearts),
|
||||||
|
Card::new(Value::Eight, Suit::Hearts),
|
||||||
|
Card::new(Value::Nine, Suit::Hearts),
|
||||||
|
Card::new(Value::Ten, Suit::Hearts),
|
||||||
|
Card::new(Value::Jack, Suit::Hearts),
|
||||||
|
Card::new(Value::Queen, Suit::Hearts),
|
||||||
|
Card::new(Value::King, Suit::Hearts),
|
||||||
|
Card::new(Value::Ace, Suit::Spades),
|
||||||
|
Card::new(Value::Two, Suit::Spades),
|
||||||
|
Card::new(Value::Three, Suit::Spades),
|
||||||
|
Card::new(Value::Four, Suit::Spades),
|
||||||
|
Card::new(Value::Five, Suit::Spades),
|
||||||
|
Card::new(Value::Six, Suit::Spades),
|
||||||
|
Card::new(Value::Seven, Suit::Spades),
|
||||||
|
Card::new(Value::Eight, Suit::Spades),
|
||||||
|
Card::new(Value::Nine, Suit::Spades),
|
||||||
|
Card::new(Value::Ten, Suit::Spades),
|
||||||
|
Card::new(Value::Jack, Suit::Spades),
|
||||||
|
Card::new(Value::Queen, Suit::Spades),
|
||||||
|
Card::new(Value::King, Suit::Spades),
|
||||||
|
];
|
|
@ -0,0 +1,94 @@
|
||||||
|
pub mod errors;
|
||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::card::Card;
|
||||||
|
use crate::card::consts::{DECK, CARDS_PER_PLAYER};
|
||||||
|
|
||||||
|
use errors::{MakeGameError, PlayError};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Game {
|
||||||
|
played: Vec<Card>,
|
||||||
|
deck: Vec<Card>,
|
||||||
|
|
||||||
|
players: Vec<Vec<Card>>,
|
||||||
|
}
|
||||||
|
impl Game {
|
||||||
|
pub fn new(decks: usize, players: usize) -> Result<Self, MakeGameError> {
|
||||||
|
const TOP_CARD: usize = 1;
|
||||||
|
if players * CARDS_PER_PLAYER + TOP_CARD >= DECK.len() * decks {
|
||||||
|
return Err(MakeGameError::InsufficientCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut deck = DECK.to_vec().repeat(decks);
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
deck.shuffle(&mut rng);
|
||||||
|
|
||||||
|
let mut players = vec![ Vec::with_capacity(7); players ];
|
||||||
|
for p in &mut players {
|
||||||
|
let range = deck.len()-CARDS_PER_PLAYER .. deck.len();
|
||||||
|
let iter = deck.drain(range);
|
||||||
|
p.extend(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// safety: this will always be safe because we checked ahead of time
|
||||||
|
// if there were enough cards
|
||||||
|
let top_card = unsafe { deck.pop().unwrap_unchecked() };
|
||||||
|
let played = vec![top_card];
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
played,
|
||||||
|
deck,
|
||||||
|
players,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play(&mut self, player: usize, cards: &[Card]) -> Result<(), PlayError> {
|
||||||
|
let player = self.players.get_mut(player).ok_or(PlayError::NoSuchPlayer)?;
|
||||||
|
|
||||||
|
let o_len = player.len();
|
||||||
|
|
||||||
|
player.retain(|card| !cards.contains(card));
|
||||||
|
|
||||||
|
if o_len - player.len() == cards.len() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(PlayError::PlayerDoesNotHaveCard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for Game {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let join_cards_by_spaces = |cards: &[Card]| join_formatted(cards, ToString::to_string, " ");
|
||||||
|
|
||||||
|
let played = join_cards_by_spaces(&self.played);
|
||||||
|
|
||||||
|
let deck = join_cards_by_spaces(&self.deck);
|
||||||
|
|
||||||
|
let players = join_formatted(
|
||||||
|
(1..).zip(&self.players),
|
||||||
|
|(i, v)| format!("{i}: {}", join_cards_by_spaces(v)),
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
write!(f, "played: {played}\ndeck: {deck}\nplayers:\n{players}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join_formatted<I, F>(iter: I, f: F, sep: &str) -> String
|
||||||
|
where
|
||||||
|
I: IntoIterator,
|
||||||
|
F: FnMut(I::Item) -> String,
|
||||||
|
{
|
||||||
|
iter.into_iter()
|
||||||
|
.map(f)
|
||||||
|
.reduce(|acc, s| acc + sep + &s)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Message {
|
||||||
|
PlayCards(usize, Vec<Card>)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum MakeGameError {
|
||||||
|
InsufficientCards,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum PlayError {
|
||||||
|
NoSuchPlayer,
|
||||||
|
PlayerDoesNotHaveCard,
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
mod game;
|
||||||
|
mod card;
|
||||||
|
mod frontend;
|
||||||
|
|
||||||
|
use std::env::args;
|
||||||
|
|
||||||
|
use game::{errors::MakeGameError, Game};
|
||||||
|
|
||||||
|
fn main() -> Result<(), MakeGameError> {
|
||||||
|
let args = args().collect::<Vec<_>>();
|
||||||
|
let decks = args[1].parse()
|
||||||
|
.expect("Invalid number of decks");
|
||||||
|
let players = args[2].parse()
|
||||||
|
.expect("Invalid number of players");
|
||||||
|
|
||||||
|
let g = Game::new(decks, players)?;
|
||||||
|
|
||||||
|
println!("{g}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
use axum::{response::IntoResponse, routing::get, Router};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
// Setup the command line interface with clap.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(name = "server", about = "A server for our wasm project!")]
|
||||||
|
struct Opt {
|
||||||
|
/// set the listen addr
|
||||||
|
#[clap(short = 'a', long = "addr", default_value = "::1")]
|
||||||
|
addr: String,
|
||||||
|
|
||||||
|
/// set the listen port
|
||||||
|
#[clap(short = 'p', long = "port", default_value = "8080")]
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let opt = Opt::parse();
|
||||||
|
|
||||||
|
let app = Router::new().route("/", get(hello));
|
||||||
|
|
||||||
|
let sock_addr = SocketAddr::from((
|
||||||
|
IpAddr::from_str(opt.addr.as_str()).unwrap_or(IpAddr::V6(Ipv6Addr::LOCALHOST)),
|
||||||
|
opt.port,
|
||||||
|
));
|
||||||
|
let tcp = TcpListener::bind(sock_addr)
|
||||||
|
.await
|
||||||
|
.expect("failed to bind to port");
|
||||||
|
|
||||||
|
println!("listening on http://{sock_addr}");
|
||||||
|
|
||||||
|
axum::serve(tcp, app)
|
||||||
|
.await
|
||||||
|
.expect("Unable to start server");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn hello() -> impl IntoResponse {
|
||||||
|
"penalty for not contacting the server correctly"
|
||||||
|
}
|
Loading…
Reference in New Issue