actor implemented
This commit is contained in:
parent
e5d3bbb628
commit
4444af9d07
13 changed files with 624 additions and 154 deletions
135
Cargo.lock
generated
135
Cargo.lock
generated
|
@ -1,5 +1,13 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "0.4.5"
|
||||
|
@ -53,6 +61,35 @@ dependencies = [
|
|||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.0-beta.1"
|
||||
source = "git+https://github.com/clap-rs/clap/#37889c661134e8286102f7d2ab3267965d010403"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap_derive 3.0.0-beta.1 (git+https://github.com/clap-rs/clap/)",
|
||||
"indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.0.0-beta.1"
|
||||
source = "git+https://github.com/clap-rs/clap/#37889c661134e8286102f7d2ab3267965d010403"
|
||||
dependencies = [
|
||||
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "1.9.3"
|
||||
|
@ -97,6 +134,14 @@ dependencies = [
|
|||
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.8"
|
||||
|
@ -105,6 +150,14 @@ dependencies = [
|
|||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
|
@ -250,6 +303,30 @@ name = "ppv-lite86"
|
|||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.9"
|
||||
|
@ -365,6 +442,11 @@ dependencies = [
|
|||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.16"
|
||||
|
@ -375,10 +457,21 @@ dependencies = [
|
|||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn-mid"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tetris"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap 3.0.0-beta.1 (git+https://github.com/clap-rs/clap/)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -386,6 +479,14 @@ dependencies = [
|
|||
"tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.42"
|
||||
|
@ -429,11 +530,31 @@ dependencies = [
|
|||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.7.0"
|
||||
|
@ -478,6 +599,7 @@ dependencies = [
|
|||
]
|
||||
|
||||
[metadata]
|
||||
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
"checksum arc-swap 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
|
||||
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
@ -486,13 +608,17 @@ dependencies = [
|
|||
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
|
||||
"checksum clap 3.0.0-beta.1 (git+https://github.com/clap-rs/clap/)" = "<none>"
|
||||
"checksum clap_derive 3.0.0-beta.1 (git+https://github.com/clap-rs/clap/)" = "<none>"
|
||||
"checksum colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
|
||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
"checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
|
||||
"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
|
||||
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||
"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
|
||||
"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
|
||||
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
@ -510,6 +636,8 @@ dependencies = [
|
|||
"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
|
||||
"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
|
||||
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
"checksum proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
|
||||
"checksum proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
|
||||
"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
|
||||
"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
|
||||
"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
|
||||
|
@ -523,11 +651,18 @@ dependencies = [
|
|||
"checksum simple_logger 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fea0c4611f32f4c2bac73754f22dca1f57e6c1945e0590dae4e5f2a077b92367"
|
||||
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
|
||||
"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
|
||||
"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616"
|
||||
"checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
|
||||
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
|
||||
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
|
|
|
@ -12,3 +12,4 @@ tokio = { version = "0.2", features = ["full"] }
|
|||
log = "0.4"
|
||||
simple_logger = "1.6"
|
||||
sdl2 = { version = "0.33.0", features = ["ttf"] }
|
||||
clap = { git = "https://github.com/clap-rs/clap/" }
|
44
src/actors/mod.rs
Normal file
44
src/actors/mod.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use crate::game::{Action, Game};
|
||||
use crate::playfield::{Matrix, PlayField};
|
||||
use crate::tetromino::{Tetromino, TetrominoType};
|
||||
use rand::RngCore;
|
||||
|
||||
pub mod qlearning;
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone)]
|
||||
pub struct State {
|
||||
matrix: Matrix,
|
||||
active_piece: Option<Tetromino>,
|
||||
held_piece: Option<TetrominoType>,
|
||||
}
|
||||
|
||||
impl From<Game> for State {
|
||||
fn from(game: Game) -> Self {
|
||||
(&game).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Game> for State {
|
||||
fn from(game: &Game) -> Self {
|
||||
game.playfield().clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlayField> for State {
|
||||
fn from(playfield: PlayField) -> Self {
|
||||
Self {
|
||||
matrix: playfield.field().clone(),
|
||||
active_piece: playfield.active_piece,
|
||||
held_piece: playfield.hold_piece().map(|t| t.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Actor {
|
||||
fn get_action<T: RngCore>(
|
||||
&self,
|
||||
rng: &mut T,
|
||||
state: &State,
|
||||
legal_actions: &[Action],
|
||||
) -> Action;
|
||||
}
|
86
src/actors/qlearning.rs
Normal file
86
src/actors/qlearning.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use crate::actors::{Actor, State};
|
||||
use crate::game::Action;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::Rng;
|
||||
use rand::RngCore;
|
||||
use std::collections::HashMap;
|
||||
pub struct QLearningAgent {
|
||||
pub learning_rate: f64,
|
||||
pub exploration_prob: f64,
|
||||
discount_rate: f64,
|
||||
q_values: HashMap<State, HashMap<Action, f64>>,
|
||||
}
|
||||
|
||||
impl Default for QLearningAgent {
|
||||
fn default() -> Self {
|
||||
QLearningAgent {
|
||||
learning_rate: 0.1,
|
||||
exploration_prob: 0.5,
|
||||
discount_rate: 1.0,
|
||||
q_values: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QLearningAgent {
|
||||
fn get_q_value(&self, state: &State, action: Action) -> f64 {
|
||||
match self.q_values.get(&state) {
|
||||
Some(action_qval) => *action_qval.get(&action).unwrap_or_else(|| &0.0),
|
||||
None => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_action_from_q_values(&self, state: &State, legal_actions: &[Action]) -> Action {
|
||||
*legal_actions
|
||||
.iter()
|
||||
.map(|action| (action, self.get_q_value(&state, *action)))
|
||||
.max_by_key(|(_, q1)| ((q1 * 1_000_000.0) as isize))
|
||||
.expect("Failed to select an action")
|
||||
.0
|
||||
}
|
||||
|
||||
fn get_value_from_q_values(&self, state: &State) -> f64 {
|
||||
*self
|
||||
.q_values
|
||||
.get(state)
|
||||
.and_then(|hashmap| {
|
||||
hashmap
|
||||
.values()
|
||||
.max_by_key(|q_val| (**q_val * 1_000_000.0) as isize)
|
||||
.or_else(|| Some(&0.0))
|
||||
})
|
||||
.unwrap_or_else(|| &0.0)
|
||||
}
|
||||
|
||||
pub fn update(&mut self, state: State, action: Action, next_state: State, reward: f64) {
|
||||
let cur_q_val = self.get_q_value(&state, action);
|
||||
let new_q_val = cur_q_val
|
||||
+ self.learning_rate
|
||||
* (reward + self.discount_rate * self.get_value_from_q_values(&next_state)
|
||||
- cur_q_val);
|
||||
if !self.q_values.contains_key(&state) {
|
||||
self.q_values.insert(state.clone(), HashMap::default());
|
||||
}
|
||||
self.q_values
|
||||
.get_mut(&state)
|
||||
.unwrap()
|
||||
.insert(action, new_q_val);
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for QLearningAgent {
|
||||
// Because doing (Nothing) is in the set of legal actions, this will never
|
||||
// be empty
|
||||
fn get_action<T: RngCore>(
|
||||
&self,
|
||||
rng: &mut T,
|
||||
state: &State,
|
||||
legal_actions: &[Action],
|
||||
) -> Action {
|
||||
if rng.gen::<f64>() < self.exploration_prob {
|
||||
*legal_actions.choose(rng).unwrap()
|
||||
} else {
|
||||
self.get_action_from_q_values(state, legal_actions)
|
||||
}
|
||||
}
|
||||
}
|
4
src/cli.rs
Normal file
4
src/cli.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
use clap::Clap;
|
||||
|
||||
#[derive(Clap)]
|
||||
pub struct Ops {}
|
54
src/game.rs
54
src/game.rs
|
@ -5,10 +5,8 @@ use crate::playfield::PlayField;
|
|||
use crate::srs::RotationSystem;
|
||||
use crate::srs::SRS;
|
||||
use crate::tetromino::Position;
|
||||
use crate::Renderable;
|
||||
use crate::TICKS_PER_SECOND;
|
||||
use log::{error, info, trace};
|
||||
use sdl2::{render::Canvas, video::Window};
|
||||
use std::fmt;
|
||||
|
||||
// I think this was correct, can't find source
|
||||
|
@ -34,6 +32,7 @@ pub struct Game {
|
|||
/// The last clear action performed, used for determining if a back-to-back
|
||||
/// bonus is needed.
|
||||
last_clear_action: ClearAction,
|
||||
line_clears: u32,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Game {
|
||||
|
@ -57,6 +56,7 @@ impl Default for Game {
|
|||
next_spawn_tick: 0,
|
||||
is_game_over: None,
|
||||
last_clear_action: ClearAction::Single, // Doesn't matter what it's initialized to
|
||||
line_clears: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,9 @@ enum ClearAction {
|
|||
}
|
||||
|
||||
impl Game {
|
||||
pub fn score(&self) -> u32 {
|
||||
self.score
|
||||
}
|
||||
pub fn is_game_over(&self) -> Option<LossReason> {
|
||||
self.is_game_over.or_else(|| {
|
||||
self.playfield
|
||||
|
@ -164,15 +167,18 @@ impl Game {
|
|||
let positions = self.playfield.lock_active_piece();
|
||||
self.is_game_over = self.is_game_over.or_else(|| {
|
||||
if positions.iter().map(|p| p.y).all(|y| y < 20) {
|
||||
println!("{:?}", positions);
|
||||
Some(LossReason::LockOut)
|
||||
trace!("Loss due to topout! {:?}", positions);
|
||||
Some(LossReason::TopOut)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if self.clear_lines() > 0 {
|
||||
println!("Lines were cleared.");
|
||||
let cleared_lines = self.clear_lines();
|
||||
if cleared_lines > 0 {
|
||||
trace!("Lines were cleared.");
|
||||
self.line_clears += cleared_lines as u32;
|
||||
self.level = (self.line_clears / 10) as u8;
|
||||
self.playfield.active_piece = None;
|
||||
self.next_spawn_tick = self.tick + LINE_CLEAR_DELAY;
|
||||
} else {
|
||||
|
@ -184,11 +190,9 @@ impl Game {
|
|||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Game {
|
||||
fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String> {
|
||||
self.playfield.render(canvas)
|
||||
pub fn playfield(&self) -> &PlayField {
|
||||
&self.playfield
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,6 +204,19 @@ pub trait Controllable {
|
|||
fn rotate_right(&mut self);
|
||||
fn hard_drop(&mut self);
|
||||
fn hold(&mut self);
|
||||
fn get_legal_actions(&self) -> Vec<Action>;
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq, Debug, Copy, Clone)]
|
||||
pub enum Action {
|
||||
Nothing, // Default value
|
||||
MoveLeft,
|
||||
MoveRight,
|
||||
SoftDrop,
|
||||
HardDrop,
|
||||
Hold,
|
||||
RotateLeft,
|
||||
RotateRight,
|
||||
}
|
||||
|
||||
impl Controllable for Game {
|
||||
|
@ -303,4 +320,21 @@ impl Controllable for Game {
|
|||
|
||||
let _ = self.playfield.try_swap_hold();
|
||||
}
|
||||
|
||||
fn get_legal_actions(&self) -> Vec<Action> {
|
||||
let mut legal_actions = vec![
|
||||
Action::MoveLeft,
|
||||
Action::MoveRight,
|
||||
Action::SoftDrop,
|
||||
Action::HardDrop,
|
||||
Action::RotateLeft,
|
||||
Action::RotateRight,
|
||||
];
|
||||
|
||||
if self.playfield.can_swap() {
|
||||
legal_actions.push(Action::Hold);
|
||||
}
|
||||
|
||||
legal_actions
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
use sdl2::pixels::Color;
|
||||
|
||||
pub const CELL_SIZE: u32 = 32;
|
||||
pub const BORDER_RADIUS: u32 = 1;
|
||||
|
||||
pub static COLOR_BACKGROUND: Color = Color::RGB(60, 60, 60);
|
||||
pub static COLOR_CYAN: Color = Color::RGB(0, 255, 255);
|
||||
pub static COLOR_YELLOW: Color = Color::RGB(255, 255, 0);
|
||||
pub static COLOR_PURPLE: Color = Color::RGB(255, 0, 255);
|
||||
pub static COLOR_GREEN: Color = Color::RGB(0, 255, 0);
|
||||
pub static COLOR_RED: Color = Color::RGB(255, 0, 0);
|
||||
pub static COLOR_BLUE: Color = Color::RGB(0, 0, 255);
|
||||
pub static COLOR_ORANGE: Color = Color::RGB(255, 127, 0);
|
||||
pub static COLOR_GRAY: Color = Color::RGB(100, 100, 100);
|
86
src/graphics/mod.rs
Normal file
86
src/graphics/mod.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use crate::tetromino::TetrominoType;
|
||||
use sdl2::{
|
||||
pixels::Color,
|
||||
render::{Canvas, RenderTarget},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
pub mod standard_renderer;
|
||||
|
||||
pub const CELL_SIZE: u32 = 32;
|
||||
pub const BORDER_RADIUS: u32 = 1;
|
||||
pub const UI_PADDING: u32 = 8;
|
||||
|
||||
pub static COLOR_BACKGROUND: Color = Color::RGB(60, 60, 60);
|
||||
pub static COLOR_CYAN: Color = Color::RGB(0, 255, 255);
|
||||
pub static COLOR_YELLOW: Color = Color::RGB(255, 255, 0);
|
||||
pub static COLOR_PURPLE: Color = Color::RGB(255, 0, 255);
|
||||
pub static COLOR_GREEN: Color = Color::RGB(0, 255, 0);
|
||||
pub static COLOR_RED: Color = Color::RGB(255, 0, 0);
|
||||
pub static COLOR_BLUE: Color = Color::RGB(0, 0, 255);
|
||||
pub static COLOR_ORANGE: Color = Color::RGB(255, 127, 0);
|
||||
pub static COLOR_GRAY: Color = Color::RGB(100, 100, 100);
|
||||
|
||||
pub trait Renderable {
|
||||
fn width(&self) -> usize;
|
||||
fn height(&self) -> usize;
|
||||
fn render<R: RenderTarget>(&self, canvas: &mut Canvas<R>) -> Result<(), String>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum MinoColor {
|
||||
Cyan,
|
||||
Yellow,
|
||||
Purple,
|
||||
Green,
|
||||
Red,
|
||||
Blue,
|
||||
Orange,
|
||||
Gray,
|
||||
}
|
||||
|
||||
impl fmt::Debug for MinoColor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Cyan => "c",
|
||||
Self::Yellow => "y",
|
||||
Self::Purple => "p",
|
||||
Self::Green => "g",
|
||||
Self::Red => "r",
|
||||
Self::Blue => "b",
|
||||
Self::Orange => "o",
|
||||
Self::Gray => "x",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Color> for MinoColor {
|
||||
fn into(self) -> Color {
|
||||
match self {
|
||||
Self::Cyan => COLOR_CYAN,
|
||||
Self::Yellow => COLOR_YELLOW,
|
||||
Self::Purple => COLOR_PURPLE,
|
||||
Self::Green => COLOR_GREEN,
|
||||
Self::Red => COLOR_RED,
|
||||
Self::Blue => COLOR_BLUE,
|
||||
Self::Orange => COLOR_ORANGE,
|
||||
Self::Gray => COLOR_GRAY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Color> for TetrominoType {
|
||||
fn into(self) -> Color {
|
||||
Into::<MinoColor>::into(self).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Color> for &TetrominoType {
|
||||
fn into(self) -> Color {
|
||||
Into::<MinoColor>::into(*self).into()
|
||||
}
|
||||
}
|
135
src/graphics/standard_renderer.rs
Normal file
135
src/graphics/standard_renderer.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use crate::{
|
||||
game::Game,
|
||||
graphics::{MinoColor, Renderable, BORDER_RADIUS, CELL_SIZE, COLOR_BACKGROUND, UI_PADDING},
|
||||
playfield::{PlayField, PLAYFIELD_HEIGHT, PLAYFIELD_WIDTH},
|
||||
tetromino::{Position, RotationState, Tetromino, TetrominoType},
|
||||
};
|
||||
use sdl2::{
|
||||
pixels::Color,
|
||||
rect::Rect,
|
||||
render::{Canvas, RenderTarget},
|
||||
video::Window,
|
||||
};
|
||||
|
||||
pub fn render(canvas: &mut Canvas<Window>, game: &Game) {
|
||||
let (x, y) = canvas.window().size();
|
||||
let game_width = game.width() as i32;
|
||||
let game_height = game.height() as i32;
|
||||
canvas.set_viewport(Some(Rect::new(
|
||||
x as i32 / 2 - game_width / 2 as i32,
|
||||
y as i32 / 2 - game_height / 2 as i32,
|
||||
game_width as u32,
|
||||
game_height as u32,
|
||||
)));
|
||||
game.render(canvas);
|
||||
}
|
||||
|
||||
impl Renderable for Game {
|
||||
fn width(&self) -> usize {
|
||||
self.playfield().width()
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.playfield().height()
|
||||
}
|
||||
|
||||
fn render<R: RenderTarget>(&self, canvas: &mut Canvas<R>) -> Result<(), String> {
|
||||
self.playfield().render(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for PlayField {
|
||||
fn width(&self) -> usize {
|
||||
(CELL_SIZE as usize) * PLAYFIELD_WIDTH
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
(CELL_SIZE as usize) * PLAYFIELD_HEIGHT
|
||||
}
|
||||
|
||||
fn render<R: RenderTarget>(&self, canvas: &mut Canvas<R>) -> Result<(), String> {
|
||||
for y in 0..PLAYFIELD_HEIGHT {
|
||||
for x in 0..PLAYFIELD_WIDTH {
|
||||
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||
canvas.fill_rect(Rect::new(
|
||||
CELL_SIZE as i32 * x as i32,
|
||||
CELL_SIZE as i32 * y as i32,
|
||||
CELL_SIZE,
|
||||
CELL_SIZE,
|
||||
))?;
|
||||
|
||||
match self.field()[y + PLAYFIELD_HEIGHT][x] {
|
||||
Some(mino) => canvas.set_draw_color(mino),
|
||||
None => canvas.set_draw_color(COLOR_BACKGROUND),
|
||||
}
|
||||
canvas.fill_rect(Rect::new(
|
||||
CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32,
|
||||
CELL_SIZE as i32 * y as i32 + BORDER_RADIUS as i32,
|
||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
match self.hold_piece() {
|
||||
Some(p) => {
|
||||
canvas.set_draw_color(p);
|
||||
canvas.fill_rect(Rect::new(-32 - UI_PADDING as i32, 0, CELL_SIZE, CELL_SIZE));
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
match self.active_piece {
|
||||
Some(piece) => piece.render(canvas)?,
|
||||
None => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Tetromino {
|
||||
fn width(&self) -> usize {
|
||||
CELL_SIZE as usize
|
||||
* match self.rotation_state {
|
||||
RotationState::O | RotationState::U => match self.piece_type {
|
||||
TetrominoType::I => 4,
|
||||
TetrominoType::O => 2,
|
||||
_ => 3,
|
||||
},
|
||||
RotationState::L | RotationState::R => match self.piece_type {
|
||||
TetrominoType::I => 1,
|
||||
_ => 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
CELL_SIZE as usize
|
||||
* match self.rotation_state {
|
||||
RotationState::O | RotationState::U => match self.piece_type {
|
||||
TetrominoType::I => 1,
|
||||
_ => 2,
|
||||
},
|
||||
RotationState::L | RotationState::R => match self.piece_type {
|
||||
TetrominoType::I => 4,
|
||||
TetrominoType::O => 2,
|
||||
_ => 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn render<R: RenderTarget>(&self, canvas: &mut Canvas<R>) -> Result<(), String> {
|
||||
for Position { x, y } in self.get_cur_occupied_spaces() {
|
||||
canvas.set_draw_color::<MinoColor>(self.piece_type.into());
|
||||
let height = y as isize - PLAYFIELD_HEIGHT as isize;
|
||||
canvas.fill_rect(Rect::new(
|
||||
CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32,
|
||||
CELL_SIZE as i32 * height as i32 + BORDER_RADIUS as i32,
|
||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
70
src/main.rs
70
src/main.rs
|
@ -1,13 +1,16 @@
|
|||
use game::{Controllable, Game, Tickable};
|
||||
use actors::*;
|
||||
use game::{Action, Controllable, Game, Tickable};
|
||||
use graphics::standard_renderer;
|
||||
use graphics::COLOR_BACKGROUND;
|
||||
use log::{debug, info, trace};
|
||||
use rand::SeedableRng;
|
||||
use sdl2::event::Event;
|
||||
use sdl2::keyboard::Keycode;
|
||||
use sdl2::{render::Canvas, video::Window};
|
||||
use simple_logger;
|
||||
use std::time::Duration;
|
||||
use tokio::time::interval;
|
||||
|
||||
mod actors;
|
||||
mod game;
|
||||
mod graphics;
|
||||
mod playfield;
|
||||
|
@ -17,13 +20,51 @@ mod tetromino;
|
|||
|
||||
const TICKS_PER_SECOND: usize = 60;
|
||||
|
||||
pub trait Renderable {
|
||||
fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String>;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
simple_logger::init()?;
|
||||
// simple_logger::init()?;
|
||||
simple_logger::init_with_level(log::Level::Info)?;
|
||||
|
||||
let mut actor = qlearning::QLearningAgent::default();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(1337);
|
||||
|
||||
let mut avg = 0.0;
|
||||
|
||||
for i in 0..100000 {
|
||||
if i % 100 == 0 {
|
||||
info!("Last 100 scores avg: {}", avg);
|
||||
avg = 0.0;
|
||||
}
|
||||
let mut game = Game::default();
|
||||
while (&game).is_game_over().is_none() {
|
||||
let cur_state = (&game).into();
|
||||
let cur_score = game.score();
|
||||
let action = actor.get_action(&mut rng, &cur_state, &((&game).get_legal_actions()));
|
||||
|
||||
match action {
|
||||
Action::Nothing => (),
|
||||
Action::MoveLeft => game.move_left(),
|
||||
Action::MoveRight => game.move_right(),
|
||||
Action::SoftDrop => game.move_down(),
|
||||
Action::HardDrop => game.hard_drop(),
|
||||
Action::Hold => game.hold(),
|
||||
Action::RotateLeft => game.rotate_left(),
|
||||
Action::RotateRight => game.rotate_right(),
|
||||
}
|
||||
|
||||
let new_state = (&game).into();
|
||||
let reward = game.score() - cur_score;
|
||||
actor.update(cur_state, action, new_state, reward as f64);
|
||||
|
||||
game.tick();
|
||||
}
|
||||
|
||||
avg += game.score() as f64 / 100.0;
|
||||
// info!("Game over with score of {}", game.score());
|
||||
}
|
||||
|
||||
actor.exploration_prob = 0.0;
|
||||
|
||||
let sdl_context = sdl2::init()?;
|
||||
let video_subsystem = sdl_context.video()?;
|
||||
let window = video_subsystem
|
||||
|
@ -44,6 +85,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
None => (),
|
||||
}
|
||||
|
||||
let cur_state = (&game).into();
|
||||
let action = actor.get_action(&mut rng, &cur_state, &((&game).get_legal_actions()));
|
||||
match action {
|
||||
Action::Nothing => (),
|
||||
Action::MoveLeft => game.move_left(),
|
||||
Action::MoveRight => game.move_right(),
|
||||
Action::SoftDrop => game.move_down(),
|
||||
Action::HardDrop => game.hard_drop(),
|
||||
Action::Hold => game.hold(),
|
||||
Action::RotateLeft => game.rotate_left(),
|
||||
Action::RotateRight => game.rotate_right(),
|
||||
}
|
||||
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. }
|
||||
|
@ -123,7 +177,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
game.tick();
|
||||
canvas.set_draw_color(COLOR_BACKGROUND);
|
||||
canvas.clear();
|
||||
game.render(&mut canvas)?;
|
||||
standard_renderer::render(&mut canvas, &game);
|
||||
canvas.present();
|
||||
interval.tick().await;
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
use crate::graphics::{BORDER_RADIUS, CELL_SIZE, COLOR_BACKGROUND};
|
||||
use crate::random::RandomSystem;
|
||||
use crate::tetromino::{MinoColor, Position, Tetromino, TetrominoType};
|
||||
use crate::Renderable;
|
||||
use sdl2::{pixels::Color, rect::Rect, render::Canvas, video::Window};
|
||||
use crate::tetromino::{Position, Tetromino, TetrominoType};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
|
||||
pub const PLAYFIELD_HEIGHT: usize = 20;
|
||||
pub const PLAYFIELD_WIDTH: usize = 10;
|
||||
|
||||
pub type Matrix = [[Option<MinoColor>; PLAYFIELD_WIDTH]; 2 * PLAYFIELD_HEIGHT];
|
||||
pub type Matrix = Vec<Vec<Option<TetrominoType>>>;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum Movement {
|
||||
Rotation,
|
||||
Gravity,
|
||||
Translation,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlayField {
|
||||
can_swap_hold: bool,
|
||||
hold_piece: Option<TetrominoType>,
|
||||
|
@ -85,10 +84,15 @@ impl PlayField {
|
|||
next_pieces.push_back(bag.get_tetromino());
|
||||
}
|
||||
|
||||
let row = [None; PLAYFIELD_WIDTH].to_vec();
|
||||
let mut field = Vec::with_capacity(2 * PLAYFIELD_HEIGHT);
|
||||
for _ in 0..2 * PLAYFIELD_HEIGHT {
|
||||
field.push(row.clone());
|
||||
}
|
||||
PlayField {
|
||||
can_swap_hold: true,
|
||||
hold_piece: None,
|
||||
field: [[None; PLAYFIELD_WIDTH]; 2 * PLAYFIELD_HEIGHT],
|
||||
field,
|
||||
active_piece: Some(active_piece),
|
||||
bag,
|
||||
next_pieces,
|
||||
|
@ -161,10 +165,9 @@ impl PlayField {
|
|||
pub fn lock_active_piece(&mut self) -> Vec<Position> {
|
||||
match &self.active_piece {
|
||||
Some(active_piece) => {
|
||||
let active_color = active_piece.get_color();
|
||||
let new_pieces = active_piece.get_cur_occupied_spaces();
|
||||
for Position { x, y } in &new_pieces {
|
||||
self.field[*y as usize][*x as usize] = Some(active_color);
|
||||
self.field[*y as usize][*x as usize] = Some(active_piece.piece_type);
|
||||
}
|
||||
|
||||
new_pieces
|
||||
|
@ -209,49 +212,27 @@ impl PlayField {
|
|||
Err(())
|
||||
}
|
||||
|
||||
pub fn can_swap(&self) -> bool {
|
||||
self.can_swap_hold
|
||||
}
|
||||
|
||||
pub fn try_clear_row(&mut self, row: usize) -> Result<(), ()> {
|
||||
if self.field[row].iter().all(|cell| cell.is_some()) {
|
||||
self.field[row] = [None; PLAYFIELD_WIDTH];
|
||||
self.field[row] = [None; PLAYFIELD_WIDTH].to_vec();
|
||||
for y in (1..=row).rev() {
|
||||
self.field[y] = self.field[y - 1];
|
||||
self.field[y] = self.field[y - 1].clone();
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for PlayField {
|
||||
fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String> {
|
||||
for y in 0..PLAYFIELD_HEIGHT {
|
||||
for x in 0..PLAYFIELD_WIDTH {
|
||||
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||
canvas.fill_rect(Rect::new(
|
||||
CELL_SIZE as i32 * x as i32,
|
||||
CELL_SIZE as i32 * y as i32,
|
||||
CELL_SIZE,
|
||||
CELL_SIZE,
|
||||
))?;
|
||||
pub fn field(&self) -> &Matrix {
|
||||
&self.field
|
||||
}
|
||||
|
||||
match self.field[y + PLAYFIELD_HEIGHT][x] {
|
||||
Some(mino) => canvas.set_draw_color(mino),
|
||||
None => canvas.set_draw_color(COLOR_BACKGROUND),
|
||||
}
|
||||
canvas.fill_rect(Rect::new(
|
||||
CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32,
|
||||
CELL_SIZE as i32 * y as i32 + BORDER_RADIUS as i32,
|
||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
match self.active_piece {
|
||||
Some(piece) => piece.render(canvas)?,
|
||||
None => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn hold_piece(&self) -> Option<&TetrominoType> {
|
||||
self.hold_piece.as_ref()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::tetromino::TetrominoType;
|
||||
use rand::{rngs::ThreadRng, seq::SliceRandom, thread_rng};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RandomSystem {
|
||||
rng: ThreadRng,
|
||||
bag: [TetrominoType; 7],
|
||||
|
|
|
@ -1,58 +1,9 @@
|
|||
use crate::{
|
||||
graphics::*,
|
||||
playfield::{PLAYFIELD_HEIGHT, PLAYFIELD_WIDTH},
|
||||
Renderable,
|
||||
};
|
||||
use sdl2::{pixels::Color, rect::Rect, render::Canvas, video::Window};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum MinoColor {
|
||||
Cyan,
|
||||
Yellow,
|
||||
Purple,
|
||||
Green,
|
||||
Red,
|
||||
Blue,
|
||||
Orange,
|
||||
Gray,
|
||||
}
|
||||
|
||||
impl fmt::Debug for MinoColor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Cyan => "c",
|
||||
Self::Yellow => "y",
|
||||
Self::Purple => "p",
|
||||
Self::Green => "g",
|
||||
Self::Red => "r",
|
||||
Self::Blue => "b",
|
||||
Self::Orange => "o",
|
||||
Self::Gray => "x",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Color> for MinoColor {
|
||||
fn into(self) -> Color {
|
||||
match self {
|
||||
Self::Cyan => COLOR_CYAN,
|
||||
Self::Yellow => COLOR_YELLOW,
|
||||
Self::Purple => COLOR_PURPLE,
|
||||
Self::Green => COLOR_GREEN,
|
||||
Self::Red => COLOR_RED,
|
||||
Self::Blue => COLOR_BLUE,
|
||||
Self::Orange => COLOR_ORANGE,
|
||||
Self::Gray => COLOR_GRAY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum TetrominoType {
|
||||
I,
|
||||
O,
|
||||
|
@ -91,7 +42,7 @@ impl Default for RotationState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Position {
|
||||
pub x: isize,
|
||||
pub y: isize,
|
||||
|
@ -133,7 +84,7 @@ impl std::ops::Sub for Position {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Tetromino {
|
||||
pub position: Position,
|
||||
pub piece_type: TetrominoType,
|
||||
|
@ -149,18 +100,6 @@ impl Tetromino {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_color(&self) -> MinoColor {
|
||||
match self.piece_type {
|
||||
TetrominoType::I => MinoColor::Cyan,
|
||||
TetrominoType::O => MinoColor::Yellow,
|
||||
TetrominoType::T => MinoColor::Purple,
|
||||
TetrominoType::S => MinoColor::Green,
|
||||
TetrominoType::Z => MinoColor::Red,
|
||||
TetrominoType::J => MinoColor::Blue,
|
||||
TetrominoType::L => MinoColor::Orange,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_start_position(tetromino_type: TetrominoType) -> Position {
|
||||
if tetromino_type == TetrominoType::I {
|
||||
Position::new(
|
||||
|
@ -366,19 +305,3 @@ impl From<TetrominoType> for Tetromino {
|
|||
Self::new(tetromino_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Tetromino {
|
||||
fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String> {
|
||||
for Position { x, y } in self.get_cur_occupied_spaces() {
|
||||
canvas.set_draw_color::<MinoColor>(self.piece_type.into());
|
||||
let height = y as isize - PLAYFIELD_HEIGHT as isize;
|
||||
canvas.fill_rect(Rect::new(
|
||||
CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32,
|
||||
CELL_SIZE as i32 * height as i32 + BORDER_RADIUS as i32,
|
||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue