Responses for exercises in Exercism.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

315 lines
9.1 KiB

#[deny(warnings, missing_docs)]
/// Errors created when in the game
#[derive(Debug, PartialEq)]
pub enum Error {
/// The throw is higher the number of pins available.
NotEnoughPinsLeft,
/// The game is over, no more throws are possible.
GameComplete,
}
/// A throw.
// I'd usually move those to their own file, but since Exercism wants a single file... here we go.
mod throw {
#[deny(warnings, missing_docs)]
use std::fmt;
#[derive(PartialEq)]
enum ThrowType {
/// This throw doesn't exist yet; the player did something (a strike, a spare) that gave
/// them another throw.
Free,
/// Pins knocked (or not, if the value is 0).
Knock(u16),
}
pub struct Throw {
/// The throw id; it is used only for our debugging reference.
id: usize,
/// The result of the throw.
result: ThrowType,
}
impl Throw {
/// Create a free Throw
pub fn new_free(id: usize) -> Self {
Self {
id: id,
result: ThrowType::Free,
}
}
/// Create a throw that knocked some pins
pub fn new(id: usize, pins: u16) -> Self {
Self {
id: id,
result: ThrowType::Knock(pins),
}
}
/// In-place update of a free throw
pub fn update(&mut self, pins: u16) {
// Silently refusing to update a non-free throw result.
if self.result == ThrowType::Free {
self.result = ThrowType::Knock(pins);
}
}
/// Indicate that the Throw is a free and has no value.
pub fn is_free_throw(&self) -> bool {
self.result == ThrowType::Free
}
/// Indicate that the player made a strike with this throw.
pub fn is_strike(&self) -> bool {
self.result == ThrowType::Knock(10)
}
/// Score of this throw.
pub fn score(&self) -> Option<u16> {
match self.result {
ThrowType::Free => None,
ThrowType::Knock(x) => Some(x),
}
}
}
/// A different debug information, so it will be easier to read.
impl fmt::Debug for Throw {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}-{}",
self.id,
match self.result {
ThrowType::Free => "Free".into(),
ThrowType::Knock(x) => format!("{}", x),
}
)
}
}
}
/// A game frame
mod frame {
#[deny(warnings, missing_docs)]
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use super::throw::Throw;
pub struct Frame {
id: usize,
throws: Vec<Rc<RefCell<Throw>>>,
}
impl Frame {
pub fn new(id: usize) -> Self {
Self {
id: id,
throws: vec![],
}
}
pub fn add_throw(&mut self, throw: &Rc<RefCell<Throw>>) {
self.throws.push(Rc::clone(throw));
}
pub fn possible_spare(&self, pins: u16) -> bool {
self.throws.len() == 1 && self.score().unwrap() + pins == 10
}
pub fn score(&self) -> Option<u16> {
if self.throws.len() == 0
|| self
.throws
.iter()
.any(|throw| throw.borrow().is_free_throw())
{
None
} else {
Some(
self.throws
.iter()
.map(|throw| throw.borrow().score().unwrap())
.sum(),
)
}
}
pub fn is_closed(&self) -> bool {
!self.has_free_throws() || self.is_a_strike() || self.throws.len() >= 2
}
pub fn accept_more_throws(&self) -> bool {
self.throws.len() != 3
}
fn has_free_throws(&self) -> bool {
self.throws
.iter()
.all(|throw| !throw.borrow().is_free_throw())
}
fn is_a_strike(&self) -> bool {
self.throws.len() == 1 && self.throws[0].borrow().is_strike()
}
}
/// A different debug information, so it will be easier to read.
impl fmt::Debug for Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}-{} ({})",
self.id,
match self.score() {
None => "??".into(),
Some(x) => format!("{}", x),
},
self.throws
.iter()
.map(|x| format!("{:?}", x))
.collect::<Vec<String>>()
.join(",")
)
}
}
}
use std::cell::RefCell;
use std::rc::Rc;
pub struct BowlingGame {
pub frames: Vec<frame::Frame>,
pub throws: Vec<Rc<RefCell<throw::Throw>>>,
}
impl BowlingGame {
pub fn new() -> Self {
Self {
frames: vec![],
throws: vec![],
}
}
pub fn roll(&mut self, pins: u16) -> Result<(), Error> {
if !self.has_free_throws() && self.is_finished() {
Err(Error::GameComplete)
} else if pins > 10 {
Err(Error::NotEnoughPinsLeft)
} else {
let mut frame_throws: Vec<Rc<RefCell<throw::Throw>>> = Vec::new();
if !self.has_free_throws() {
let throw = self.add_new_throw(pins);
frame_throws.push(throw);
} else {
let reused_throw = self.reuse_free_throw(pins);
frame_throws.push(reused_throw);
}
if pins == 10 {
// On a strike, the player get two "free" throws.
let last_id = self.throws.len();
frame_throws.push(Rc::new(RefCell::new(throw::Throw::new_free(last_id + 1))));
frame_throws.push(Rc::new(RefCell::new(throw::Throw::new_free(last_id + 2))));
} else if self.spare(pins) {
let last_id = self.throws.len();
frame_throws.push(Rc::new(RefCell::new(throw::Throw::new_free(last_id + 1))));
}
self.push_throws_to_last_frame(frame_throws.as_slice());
self.push_free_throws(frame_throws.as_slice());
dbg!(&self.throws);
dbg!(&self.frames);
Ok(())
}
}
/// Add a new throw to the throw list.
pub fn add_new_throw(&mut self, pins: u16) -> Rc<RefCell<throw::Throw>> {
let last_id = self.throws.len() + 1;
let new_throw = Rc::new(RefCell::new(throw::Throw::new(last_id, pins)));
self.throws.push(Rc::clone(&new_throw));
Rc::clone(&new_throw)
}
/// Reuse on the free throws in the throw list instead of creating a new one.
pub fn reuse_free_throw(&self, pins: u16) -> Rc<RefCell<throw::Throw>> {
let throw = self
.throws
.iter()
.filter(|throw| throw.borrow().is_free_throw())
.take(1)
.next()
.expect("There are no free throws for reuse");
throw.borrow_mut().update(pins);
throw.clone()
}
/// A a throw to the last available frame.
pub fn push_throws_to_last_frame(&mut self, throws: &[Rc<RefCell<throw::Throw>>]) {
if self.frames.len() == 0
|| (self.frames.last().unwrap().is_closed() && self.frames.len() < 10)
{
let last_id = self.frames.len();
let mut new_frame = frame::Frame::new(last_id + 1);
for throw in throws {
new_frame.add_throw(&Rc::clone(&throw));
}
self.frames.push(new_frame);
} else if self.frames.last().unwrap().accept_more_throws() {
let last_frame = self.frames.last_mut().unwrap();
for throw in throws {
last_frame.add_throw(&Rc::clone(&throw));
}
}
}
pub fn push_free_throws(&mut self, throws: &[Rc<RefCell<throw::Throw>>]) {
for throw in throws.iter().filter(|throw| throw.borrow().is_free_throw()) {
self.throws.push(Rc::clone(throw));
}
}
/// Return the game score, but only if the game is fininshed.
pub fn score(&self) -> Option<u16> {
if !self.is_finished() {
None
} else {
Some(self.frames.iter().map(|x| x.score().unwrap_or(0)).sum())
}
}
/// Check if the game is fininshed.
pub fn is_finished(&self) -> bool {
self.frames.len() == 10
&& self
.frames
.iter()
.last()
.map_or(false, |frame| frame.is_closed())
}
pub fn spare(&self, pins: u16) -> bool {
self.frames
.last()
.map_or(false, |frame| frame.possible_spare(pins))
}
/// Check if there are any free throws available.
pub fn has_free_throws(&self) -> bool {
self.throws
.iter()
.any(|throw| throw.borrow().is_free_throw())
}
}