Initial commit

main
nick 2024-07-26 13:39:38 -04:00
parent a3c00e2fcd
commit fa6755b647
12 changed files with 1563 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target/
dist/

1215
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
Cargo.toml Normal file
View File

@ -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'

6
frontend/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "frontend"
version = "0.1.0"
edition = "2021"
[dependencies]

3
frontend/src/main.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

20
server/Cargo.toml Normal file
View File

@ -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"

78
server/pre-server/card.rs Normal file
View File

@ -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",
}
)
}
}

View File

@ -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),
];

94
server/pre-server/game.rs Normal file
View File

@ -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>)
}

View File

@ -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,
}

21
server/pre-server/main.rs Normal file
View File

@ -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(())
}

46
server/src/main.rs Normal file
View File

@ -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"
}