Skip to content

Commit

Permalink
Added basic move logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Michal Salasek authored and tompsota committed Jan 30, 2022
1 parent 65727b7 commit 3f0b3b6
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ Cargo.lock
# Build generated by trunk serve
**/*/dist/

.idea/

# Node modules used for tailwind setup
node_modules/
1 change: 1 addition & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ dotenv = "0.15.0"
env_logger = "0.9.0"
mongodb = "2.1.0"
futures = "0.3.19"
rand = "0.8.4"
1 change: 1 addition & 0 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use tokio::sync::Mutex;
mod components;
mod models;
mod types;
mod utils;

use models::app_data::AppData;

Expand Down
2 changes: 1 addition & 1 deletion server/src/models/color.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
pub enum Color {
Red,
Green,
Expand Down
183 changes: 178 additions & 5 deletions server/src/models/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,185 @@ use mongodb::bson::DateTime;
use serde::{Deserialize, Serialize};

use super::player::Player;
use crate::models::color::Color;

#[derive(Debug, Serialize, Deserialize)]
pub struct Game {
pub id: String,
pub started_at: DateTime,
pub finished_at: Option<DateTime>,
pub fields: Vec<Field>,
pub players: Vec<Player>,
pub id: String,
pub started_at: DateTime,
pub finished_at: Option<DateTime>,
pub fields: Vec<Field>,
pub players: Vec<Player>,
pub current_player: Color
}


impl Game {

pub fn update_current_player(&mut self) {
self.current_player = match self.current_player {
Color::Yellow => Color::Blue,
Color::Blue => Color::Red,
Color::Red => Color::Green,
Color::Green => Color::Yellow
}
}

// how many steps we need to make to reach the first field of player's home
// for yellow starting at offset = 0, we need to make 40 - position steps
// position = index in the Vec<Field> fields [0 ; 39]
// - if yellow piece is at position 39, it is right in front of its home
pub fn distance_from_home(&self, position: usize, color: Color) -> usize {
self.fields.len() - position + get_offset(color)
}

// returns size of the home column (finish)
pub fn get_home_size(&self) -> usize {
match self.players.get(0) {
Some(player) => player.home.len(),
None => 4
}
}

// we assume home_offset is valid
pub fn get_home_field(&self, player_color: Color, home_offset: usize) -> &Field {
let player = self.players.iter().filter(|&player| player.color == player_color).next().unwrap();
&player.home[home_offset]
}

// we can use the following (simplest) board as an example:
// https://www.vectorstock.com/royalty-free-vector/ludo-board-game-vector-8703408
// there is a clock-wise ordering: Yellow, Blue, Red, Green
// there are 40 fields in the main 'board' (if we change the board - 56 fields e.g.),
// we have to adjust the constants
pub fn get_offset(&self, color: Color) -> usize {
let offset = (self.fields.len() / 4) as usize;
match color {
Color::Yellow => 0,
Color::Blue => offset,
Color::Red => offset * 2,
Color::Green => offset * 3
}
}

pub fn is_a_valid_move(&self, position: usize, dice_value: usize) -> MoveResultType {

if dice_value == 6 {
...
}

// if player threw 6 and decided to move his piece from the start, there are two options [*]:
// a) if the field at offset is empty:
// 1. place our piece at offset
// 2. decrease pieces_at_start by one for a specific player (color)
// b) if the field is occupied by:
// 1) our own piece - we can't move there, invalid move
// 2) opponent's piece - we can move there and remove his piece
// - the same actions as for a) + increase pieces_at_start for the player whose piece
// we have just removed (sent to start)

// [*] another thing is:
// is player able to send a message to move a piece from his start, even if he has
// no pieces at the start anymore (i.e. we might have add a check for the situation,
// when pieces_at_start = 0)


// do we always obtain Game (and fields ...) from DB for every turn ?
// and do we update the DB after every move as well (when the game/board changed) ?
// adding bonus throws after getting 6 is not solved yet


let distance_from_home = distance_from_home(position, self.current_player);

match dice_value < distance_from_home {
// is within main 'board'
true => {
new_position = position + dice_value;
match self.is_field_empty(position) {
true => MoveResultType::Success,
false => match self.is_players_piece(position, self.current_player) {
true => MoveResultType::Error(String::from("Our piece already occupies this position")),
false => MoveResultType::Success
}
}

// if piece at position:
// a) is empty, we can go there:
// 1. clear field at 'position'
// 2. update field at 'new_position'
// b) is occupied by:
// 1) our own piece - we can't go there, not a valid move
// 2) opponent's piece - we can move there (clear field at 'position', update field
//
},

// reaches home - validity of move is based on home
false => {
// first we check a situation where we overshoot home
if dice_value >= distance_from_home + self.get_home_size() {
return MoveResultType::Error(String::from("Would overshoot home."))
}

// offset in player's home column (if piece is right in front of home - distance = 1,
// and we throw a 1, we would reach the first home field
let home_offset = dice_value - distance_from_home;
match self.get_home_field(self.current_player, home_offset) {
Some(_) => MoveResultType::Error(String::from("Home field is already occupied.")),
None => MoveResultType::Success
}

// if field at home[home_offset] is occupied, invalid move
// otherwise we move our piece to that position:
// place our piece at home[home_offset] and remove original piece from fields[position]
}
}
}


// // returns whether a field specified by <position> is is occupied by a piece with <color>
// pub fn is_players_piece(&self, position: usize, player_color: Color) -> bool {
// match self.fields.get(position) {
// Some(field) => match field {
// Some(color) => color == player_color,
// None => false
// }
// None => false
// }
// }

// returns whether a field specified by <position> is is occupied by a piece with <color>
pub fn is_players_piece(&self, position: usize) -> bool {
match self.fields.get(position) {
Some(field) => match field {
Some(color) => color == self.current_player,
None => false
}
None => false
}
}

// returns whether a field is empty
pub fn is_field_empty(&self, position: usize) -> bool {
match self.fields.get(position) {
Some(field) => match field {
Some(_) => false,
None => true
}
None => false
}
}

pub fn get_player(&self) -> &Player {
self.players.iter().filter(|&player| player.color == self.current_player).next().unwrap()
}

pub fn is_current_play_AI(&self) -> bool {
self.AI_players.contains
}
}


pub enum MoveResultType {
Success,
Error(String)
}
1 change: 1 addition & 0 deletions server/src/models/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ pub struct Player {
pub color: Color,
pub pawns_at_start: u32,
pub home: Vec<Field>,
pub is_bot: bool
}
172 changes: 172 additions & 0 deletions server/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@


#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ClientMessage {
// CreateRoom(...),
// JoinRoom(...),
ThrowDice,
MoveFigure(i32),
PlaceFigure
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ServerMessage {
JoinedRoom {
room_name: String,
players: Vec<(String, u32, bool)>,
active_player: usize,
player_index: usize,
board: Vec<((i32, i32), Piece)>,
pieces: Vec<Piece>,
},
JoinFailed(String),
Chat {
from: String,
message: String,
},
Information(String),
NewPlayer(String),
PlayerReconnected(usize),
PlayerDisconnected(usize),
PlayerTurn(usize),
Played(Vec<(Piece, i32, i32)>),
Swapped(usize),
MoveAccepted(Vec<Piece>),
MoveRejected,
PlayerScore {
delta: u32,
total: u32,
},
PiecesRemaining(usize),
ItsOver(usize),
}

use crate::models::color::Color;
use crate::types::Field;
use crate::models::game::{Game, MoveResultType};
use rand::Rng;


//
// // could be a method of Game
// // returns player's home Vec<Field> based on their color
// pub fn get_home(color: Color) -> Vec<Field> {
//
// }


pub fn get_dice_value() -> usize {
let mut rng = rand::thread_rng();
rng.gen_range(1..7)
}

pub fn throw_dice() -> usize {
let mut dice_value: usize = 0;
// player/client sends MessageType::ThrowDice
<< message exchange >>
match get_dice_value() {
6 => {
dice_value += 6;
<< message exchange >>;
match get_dice_value() {
6 => {
dice_value += 6;
<< message exchange >>;
match get_dice_value() {
// if we throw 6 three times, it gets reset
6 => {
dice_value = 0;
<< message exchange >>
},
n => dice_value += n;
}
},
n => dice_value += n;
}
},
n => dice_value += n
}

dice_value
}

pub fn make_a_move() {

let mut game: Game = find_game(id);

let mut player = game.get_player();

if player.is_bot {
return make_a_move_bot()
}

// dice_value
let dice_value = throw_dice();
let position: usize = message_from_client/player();


// throw a dice and get position (which piece to move) from player
// different behaviour for AI (special attribute in Game? AI_player?
// - if AI_players contains game.current_player => it is AI

match game.is_a_valid_move(position, dice_value) {
MoveResultType::Success => {
game.execute_move();
game.update_current_player();
update_db(...)
},
MoveResultType::Error(err) => send/broadcast_error_message(err)
}

// throw dice (generate 1-6, and inform the player(s) - send a message)
// wait for a message from player (his choice of figure for example)

// je zalozene na loopoch? vzdy cakame na urcity typ spravy od klienta:

// vzdy ked obdrzime message - deserializovat, a podla typu message nieco spravit
// MessageType::ThrowDice
// MessageType::MoveFigure(position)
// napr. ak klient posle ThrowDice message, tak musi nasledovat MoveFigure message s poziciou figurky

// ak klient posle zlu poziciu (napr. field je empty alebo figurka patri superovi - ak to umozni frontend),
// tak posleme klientovi spravu o 'chybe' - 'You can only move your own pieces.'


// loop kym nedostaneme ThrowDice message (cez match MessageType) {
// ThrowDice => 1. vygenerujeme hodnotu 1-6
// 2. checkneme, ci ma hrac valid moves:
// - ak nie, posleme NoMoves message, nastavime dalsieho hraca a return
// - ak ano, len breakneme loop a cakame na dalsiu spravu od klienta
// _ => 1. odosleme message, ze najskor treba hodit kostkou? stale sme v loope
// }
//
// << mame dice_value >>
//
// loop kym nedostaneme validnu MovePiece message {} - ci position oznacuje policko s nasou figurkou
//

// co ak klienta nema ziadne volne tahy? automaticky by sme ho mali skipnut
// (t.j. message pre klienta A / broadcast pre vsetkych klientov, ze:
// > 'Player A has no available moves, skipping.'
// > 'Next player - Player B.'

// player chooses a piece to move (might choose figure at start)
// - special coordinate (-1), or a specific message?
// - if the player doesn't throw a 6, should the choice for getting a piece into a field be
// grayed out?
// >>> Use a special MessageType (PlaceFigure)

// if a player throws a 6, he can:
// a) get a piece from start to field - doesn't get a bonus throw
// b) decides to move one of his pieces in the field - gets an extra throw (applies to the same figure ??)


// ako ukladat aktualneho / nasledujuceho hraca? v DB
// pri ukonceni tahu by sa mal vo frontende prepnut dalsi hrac (napr. podla svojej farby vs. current_player
// po aktualizacii) - a napr. 'zasednut' tlacitko, ktore normalne umozni hodit kostkou
// zasleme spravu nasledujucemu hracovi, ze je na rade (napr. CurrentPlayer)
// a hraci, ktory skoncil tah teraz posleme spravu, ze nie je na rade (NotCurrentPlayer)


// pridat .idea do gitignore
}

0 comments on commit 3f0b3b6

Please sign in to comment.