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