azul: implement main structs, scoring, bonus and initializing game object
This commit is contained in:
parent
9005fe9984
commit
9f38d6612e
|
@ -442,9 +442,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.1.0"
|
version = "2.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
@ -629,7 +629,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
name = "ozai"
|
name = "ozai"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"rand",
|
||||||
"rocket",
|
"rocket",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -989,9 +991,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.112"
|
version = "1.0.113"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed"
|
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
|
@ -7,4 +7,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { version = "0.5.0", features = ["json"] }
|
rocket = { version = "0.5.0", features = ["json"] }
|
||||||
|
serde = "1.0.196"
|
||||||
|
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
|
@ -0,0 +1,462 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use rand::distributions::WeightedIndex;
|
||||||
|
use rand::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
struct GameState {
|
||||||
|
current_player: usize,
|
||||||
|
bag: TileSet,
|
||||||
|
lid: TileSet,
|
||||||
|
factories: Vec<TileSet>,
|
||||||
|
market: TileSetWithStart,
|
||||||
|
players: Vec<Player>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameState {
|
||||||
|
pub fn new(n_players: usize) -> Result<GameState, &'static str> {
|
||||||
|
if n_players < 2 {
|
||||||
|
return Err("Can't create game with less than two players");
|
||||||
|
}
|
||||||
|
if n_players > 4 {
|
||||||
|
return Err("Can't create game with more than 4 players");
|
||||||
|
}
|
||||||
|
if n_players != 2 {
|
||||||
|
return Err("Only two players is currently supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
let n_factories = 1 + 2 * n_players;
|
||||||
|
|
||||||
|
let mut factories: Vec<TileSet> = Vec::with_capacity(n_factories);
|
||||||
|
|
||||||
|
// let mut factories
|
||||||
|
for _ in 0..n_factories {
|
||||||
|
factories.push(TileSet::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut players = Vec::with_capacity(n_players);
|
||||||
|
|
||||||
|
for _ in 0..n_players {
|
||||||
|
players.push(Player::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
let game = GameState {
|
||||||
|
current_player: 0,
|
||||||
|
bag: TileSet {
|
||||||
|
blue: 20,
|
||||||
|
yellow: 20,
|
||||||
|
red: 20,
|
||||||
|
black: 20,
|
||||||
|
white: 20,
|
||||||
|
},
|
||||||
|
lid: TileSet::default(),
|
||||||
|
factories: factories,
|
||||||
|
market: TileSetWithStart {
|
||||||
|
start: 1,
|
||||||
|
blue: 0,
|
||||||
|
yellow: 0,
|
||||||
|
red: 0,
|
||||||
|
black: 0,
|
||||||
|
white: 0,
|
||||||
|
},
|
||||||
|
players: players,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(game)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills the factories from the bag
|
||||||
|
/// Will replenish the bag from the lid when empty
|
||||||
|
/// Will return with partially filled factories if out of tiles
|
||||||
|
fn fill(&mut self, mut rng: StdRng) -> Result<(), &'static str> {
|
||||||
|
for factory in &self.factories {
|
||||||
|
if factory.len() != 0 {
|
||||||
|
return Err("Cannot fill, factories are not empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for factory in &mut self.factories {
|
||||||
|
for _ in 0..4 {
|
||||||
|
if self.bag.is_empty() && !self.lid.is_empty() {
|
||||||
|
self.bag = self.lid.clone();
|
||||||
|
self.lid = TileSet::default();
|
||||||
|
} else if self.bag.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
let choices = [
|
||||||
|
Color::Blue,
|
||||||
|
Color::Yellow,
|
||||||
|
Color::Red,
|
||||||
|
Color::Black,
|
||||||
|
Color::White,
|
||||||
|
];
|
||||||
|
let weights = [
|
||||||
|
self.bag.blue,
|
||||||
|
self.bag.yellow,
|
||||||
|
self.bag.red,
|
||||||
|
self.bag.black,
|
||||||
|
self.bag.white,
|
||||||
|
];
|
||||||
|
|
||||||
|
let dist = WeightedIndex::new(&weights).unwrap();
|
||||||
|
let picked = choices[dist.sample(&mut rng)];
|
||||||
|
|
||||||
|
self.bag.add_color(picked, 1)?;
|
||||||
|
factory.add_color(picked, 1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scores the game and clears the boards into the lid
|
||||||
|
/// Doesn't check if game is ready to be scored!
|
||||||
|
fn score(&mut self) -> Result<(), &'static str> {
|
||||||
|
for i in 0..self.players.len() {
|
||||||
|
let player = &mut self.players[i];
|
||||||
|
|
||||||
|
for row in 0..5 {
|
||||||
|
if player.pattern_lines[row].len() == (row + 1) {
|
||||||
|
let color = player.pattern_lines[row]
|
||||||
|
.color
|
||||||
|
.ok_or("patternline filled with None color")?;
|
||||||
|
let index = Player::get_wall_index(row, color)?;
|
||||||
|
player.wall[row][index] = true;
|
||||||
|
player.points += player.connected(row, index);
|
||||||
|
|
||||||
|
self.lid.add_color(color, row)?;
|
||||||
|
player.pattern_lines[row] = PatternLine::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let negative = match player.floor.len() {
|
||||||
|
0 => 0,
|
||||||
|
1 => 1,
|
||||||
|
2 => 2,
|
||||||
|
3 => 4,
|
||||||
|
4 => 6,
|
||||||
|
5 => 8,
|
||||||
|
6 => 11,
|
||||||
|
_ => 14,
|
||||||
|
};
|
||||||
|
player.points -= negative;
|
||||||
|
|
||||||
|
if player.floor.start == 1 {
|
||||||
|
self.current_player = i;
|
||||||
|
player.floor.start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lid += player.floor.try_into()?;
|
||||||
|
player.floor = TileSetWithStart::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates end-of-game bonus
|
||||||
|
fn bonus_score(player: &Player) -> Result<usize, &'static str> {
|
||||||
|
let mut bonus = 0;
|
||||||
|
|
||||||
|
// Horizontal
|
||||||
|
for row in player.wall {
|
||||||
|
if row.iter().all(|&x| x == true) {
|
||||||
|
bonus += 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical
|
||||||
|
for column in 0..5 {
|
||||||
|
let mut tiles = 0;
|
||||||
|
for row in 0..5 {
|
||||||
|
if player.wall[row][column] == true {
|
||||||
|
tiles += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tiles == 5 {
|
||||||
|
bonus += 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5 of a color
|
||||||
|
for color in Color::Blue.into_iter() {
|
||||||
|
let mut tiles = 0;
|
||||||
|
for row in 0..5 {
|
||||||
|
let index = Player::get_wall_index(row, color)?;
|
||||||
|
if player.wall[row][index] == true {
|
||||||
|
tiles += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tiles == 5 {
|
||||||
|
bonus += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(bonus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, Copy)]
|
||||||
|
struct TileSet {
|
||||||
|
blue: usize,
|
||||||
|
yellow: usize,
|
||||||
|
red: usize,
|
||||||
|
black: usize,
|
||||||
|
white: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileSet {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.blue + self.yellow + self.red + self.black + self.white
|
||||||
|
}
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_color(&mut self, color: Color, n: usize) -> Result<(), &'static str> {
|
||||||
|
match color {
|
||||||
|
Color::Blue => self.blue += n,
|
||||||
|
Color::Yellow => self.yellow += n,
|
||||||
|
Color::Red => self.red += n,
|
||||||
|
Color::Black => self.black += n,
|
||||||
|
Color::White => self.white += n,
|
||||||
|
Color::Start => return Err("tried to add Start tiles to TileSet"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for TileSet {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, other: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
blue: self.blue + other.blue,
|
||||||
|
yellow: self.yellow + other.yellow,
|
||||||
|
red: self.red + other.red,
|
||||||
|
black: self.black + other.black,
|
||||||
|
white: self.white + other.white,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign for TileSet {
|
||||||
|
fn add_assign(&mut self, other: Self) {
|
||||||
|
*self = *self + other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<TileSetWithStart> for TileSet {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(value: TileSetWithStart) -> Result<Self, Self::Error> {
|
||||||
|
if value.start == 0 {
|
||||||
|
Ok(TileSet {
|
||||||
|
blue: value.blue,
|
||||||
|
yellow: value.yellow,
|
||||||
|
red: value.red,
|
||||||
|
black: value.black,
|
||||||
|
white: value.white,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("Can't convert, tileset had a start tile inside")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, Copy)]
|
||||||
|
struct TileSetWithStart {
|
||||||
|
start: usize,
|
||||||
|
blue: usize,
|
||||||
|
yellow: usize,
|
||||||
|
red: usize,
|
||||||
|
black: usize,
|
||||||
|
white: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileSetWithStart {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.blue + self.yellow + self.red + self.black + self.white + self.start
|
||||||
|
}
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
struct Player {
|
||||||
|
points: u8,
|
||||||
|
pattern_lines: [PatternLine; 5],
|
||||||
|
wall: Wall,
|
||||||
|
floor: TileSetWithStart,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
fn get_wall_index(row: usize, color: Color) -> Result<usize, &'static str> {
|
||||||
|
match (row, color) {
|
||||||
|
(0, Color::Blue) => Ok(0),
|
||||||
|
(0, Color::Yellow) => Ok(1),
|
||||||
|
(0, Color::Red) => Ok(2),
|
||||||
|
(0, Color::Black) => Ok(3),
|
||||||
|
(0, Color::White) => Ok(4),
|
||||||
|
|
||||||
|
(1, Color::Blue) => Ok(1),
|
||||||
|
(1, Color::Yellow) => Ok(2),
|
||||||
|
(1, Color::Red) => Ok(3),
|
||||||
|
(1, Color::Black) => Ok(4),
|
||||||
|
(1, Color::White) => Ok(0),
|
||||||
|
|
||||||
|
(2, Color::Blue) => Ok(2),
|
||||||
|
(2, Color::Yellow) => Ok(3),
|
||||||
|
(2, Color::Red) => Ok(4),
|
||||||
|
(2, Color::Black) => Ok(0),
|
||||||
|
(2, Color::White) => Ok(1),
|
||||||
|
|
||||||
|
(3, Color::Blue) => Ok(3),
|
||||||
|
(3, Color::Yellow) => Ok(4),
|
||||||
|
(3, Color::Red) => Ok(0),
|
||||||
|
(3, Color::Black) => Ok(1),
|
||||||
|
(3, Color::White) => Ok(2),
|
||||||
|
|
||||||
|
(4, Color::Blue) => Ok(4),
|
||||||
|
(4, Color::Yellow) => Ok(0),
|
||||||
|
(4, Color::Red) => Ok(1),
|
||||||
|
(4, Color::Black) => Ok(2),
|
||||||
|
(4, Color::White) => Ok(3),
|
||||||
|
|
||||||
|
_ => return Err("Not a valid tile on the wall"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// given a coordinate on the board, returns how many tiles are in its group
|
||||||
|
fn connected(&self, row: usize, column: usize) -> u8 {
|
||||||
|
// This implementation scans for contigious runs of tiles
|
||||||
|
// until it finds the group the tile we're checking is part of
|
||||||
|
// Does this for both the row and the column
|
||||||
|
// then returns the sum
|
||||||
|
|
||||||
|
let wall = self.wall;
|
||||||
|
|
||||||
|
if wall[row][column] == false {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sum = 0;
|
||||||
|
|
||||||
|
// Count connected tiles in the on the row
|
||||||
|
let mut count = 0;
|
||||||
|
let mut active = false;
|
||||||
|
for i in 0..5 {
|
||||||
|
if active == true && wall[row][i] == false {
|
||||||
|
break;
|
||||||
|
} else if wall[row][i] == false {
|
||||||
|
count = 0;
|
||||||
|
} else if (row, i) == (row, column) {
|
||||||
|
active = true;
|
||||||
|
count += 1;
|
||||||
|
} else {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum += count;
|
||||||
|
|
||||||
|
// Count connected tiles in the column
|
||||||
|
let mut count = 0;
|
||||||
|
let mut active = false;
|
||||||
|
for i in 0..5 {
|
||||||
|
if active == true && wall[i][column] == false {
|
||||||
|
break;
|
||||||
|
} else if wall[i][column] == false {
|
||||||
|
count = 0;
|
||||||
|
} else if (i, column) == (row, column) {
|
||||||
|
active = true;
|
||||||
|
count += 1;
|
||||||
|
} else {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum += count;
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
struct PatternLine {
|
||||||
|
color: Option<Color>,
|
||||||
|
number: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatternLine {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||||
|
enum Color {
|
||||||
|
Start,
|
||||||
|
Blue,
|
||||||
|
Yellow,
|
||||||
|
Red,
|
||||||
|
Black,
|
||||||
|
White,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for Color {
|
||||||
|
type Item = Color;
|
||||||
|
type IntoIter = ColorIter;
|
||||||
|
|
||||||
|
/// iterates through blue, yellow, red, black, and white
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
ColorIter { current: self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ColorIter {
|
||||||
|
current: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ColorIter {
|
||||||
|
type Item = Color;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Color> {
|
||||||
|
match self.current {
|
||||||
|
Color::Blue => {
|
||||||
|
let next = Color::Yellow;
|
||||||
|
self.current = next;
|
||||||
|
Some(next)
|
||||||
|
}
|
||||||
|
Color::Yellow => {
|
||||||
|
let next = Color::Red;
|
||||||
|
self.current = next;
|
||||||
|
Some(next)
|
||||||
|
}
|
||||||
|
Color::Red => {
|
||||||
|
let next = Color::Black;
|
||||||
|
self.current = next;
|
||||||
|
Some(next)
|
||||||
|
}
|
||||||
|
Color::Black => {
|
||||||
|
let next = Color::White;
|
||||||
|
self.current = next;
|
||||||
|
Some(next)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Row = [bool; 5];
|
||||||
|
type Wall = [Row; 5];
|
||||||
|
|
||||||
|
struct GameMove {
|
||||||
|
player: u8,
|
||||||
|
market: bool,
|
||||||
|
factory: u8,
|
||||||
|
color: Color,
|
||||||
|
pattern_line: usize,
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ use rocket::serde::{json::Json, Serialize};
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
|
mod azul;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index() -> String {
|
fn index() -> String {
|
||||||
"Hello, world!".to_string()
|
"Hello, world!".to_string()
|
||||||
|
|
Loading…
Reference in New Issue