Skip to content

Instantly share code, notes, and snippets.

@loloof64
Last active July 6, 2019 21:45
Show Gist options
  • Save loloof64/85a794d120cffe347535ad147cc4a7b8 to your computer and use it in GitHub Desktop.
Save loloof64/85a794d120cffe347535ad147cc4a7b8 to your computer and use it in GitHub Desktop.
TicTacToe in Rust+Yew (Launching with cargo web)
[package]
name = "tic-tac-toe-asm"
version = "0.1.0"
authors = ["Laurent Bernabé <[email protected]>"]
edition = "2018"
[dependencies]
yew = "0.6.0"
stdweb = "0.4.17"
#![recursion_limit = "128"]
use std::f64::consts::PI;
use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender};
use stdweb::web::{document, IParentNode, CanvasRenderingContext2d, FillRule};
use stdweb::web::html_element::CanvasElement;
use stdweb::unstable::TryInto;
use stdweb::web::event::{ClickEvent, IMouseEvent};
const CELLS_SIZE : i32 = 100;
#[derive(Copy, Clone)]
pub enum Player {
Circle,
Cross,
None,
}
pub struct Model {
current_player: Player,
grid: [[Player; 3]; 3],
}
pub enum Msg {
Play(ClickEvent),
Reset,
}
impl Model {
fn get_canvas_context(&self) -> CanvasRenderingContext2d {
let canvas: CanvasElement =
document()
.query_selector("#render_zone")
.expect("Failed to get render zone !")
.expect("Failed to get render zone !")
.try_into()
.expect("Failed to get render zone !");
let context: CanvasRenderingContext2d =
canvas.get_context().expect("Failed to get render zone context !");
context
}
fn draw_piece(&self, piece_type: Player, rank: i8, file: i8)
{
match piece_type {
Player::Cross => self.draw_cross(rank, file),
Player::Circle => self.draw_circle(rank, file),
_ => {},
};
}
fn draw_circle(&self, rank: i8, file: i8)
{
let context = self.get_canvas_context();
context.set_fill_style_color("#ff0000");
context.begin_path();
context.arc(
(CELLS_SIZE as f64) * ((file as f64) + 0.5),
(CELLS_SIZE as f64) * ((rank as f64) + 0.5),
(CELLS_SIZE as f64) * 0.4,
0.0,
2.0*PI,
false,
);
context.fill(FillRule::NonZero);
context.set_fill_style_color("#ffffff");
context.begin_path();
context.arc(
(CELLS_SIZE as f64) * ((file as f64) + 0.5),
(CELLS_SIZE as f64) * ((rank as f64) + 0.5),
(CELLS_SIZE as f64) * 0.3,
0.0,
2.0*PI,
false,
);
context.fill(FillRule::NonZero);
}
fn draw_cross(&self, rank: i8, file: i8)
{
let context = self.get_canvas_context();
context.save();
context.set_line_width(3.0);
context.set_fill_style_color("#0000ff");
context.begin_path();
context.move_to(
(CELLS_SIZE as f64) * ((file as f64) + 0.1),
(CELLS_SIZE as f64) * ((rank as f64) + 0.1),
);
context.line_to(
(CELLS_SIZE as f64) * ((file as f64) + 0.9),
(CELLS_SIZE as f64) * ((rank as f64) + 0.9),
);
context.stroke();
context.begin_path();
context.move_to(
(CELLS_SIZE as f64) * ((file as f64) + 0.1),
(CELLS_SIZE as f64) * ((rank as f64) + 0.9),
);
context.line_to(
(CELLS_SIZE as f64) * ((file as f64) + 0.9),
(CELLS_SIZE as f64) * ((rank as f64) + 0.1),
);
context.stroke();
context.restore();
}
fn reset_zone(&self)
{
let context = self.get_canvas_context();
context.clear_rect(0.0, 0.0, (CELLS_SIZE as f64) * 3.0, (CELLS_SIZE as f64) * 3.0);
context.set_fill_style_color("#000");
for col in 1..3 {
context.begin_path();
context.move_to((CELLS_SIZE as f64) * (col as f64), 0.0);
context.line_to((CELLS_SIZE as f64) * (col as f64), (CELLS_SIZE as f64) * 3.0);
context.stroke();
}
for row in 1..3 {
context.begin_path();
context.move_to(0.0, (CELLS_SIZE as f64) * (row as f64));
context.line_to((CELLS_SIZE as f64) * 3.0, (CELLS_SIZE as f64) * (row as f64));
context.stroke();
}
}
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
Model {
current_player: Player::None,
grid: [[Player::None; 3]; 3],
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Play(event) => {
let file = ((event.client_x() as f64) / (CELLS_SIZE as f64)) as i8;
let rank = ((event.client_y() as f64) / (CELLS_SIZE as f64)) as i8;
if file < 0 || file > 2 { return false; };
if rank < 0 || rank > 2 { return false; };
let cell_empty = match self.grid[rank as usize][file as usize]{
Player::None => true,
_ => false,
};
if cell_empty
{
self.grid[rank as usize][file as usize] = self.current_player;
self.draw_piece(self.current_player, rank, file);
self.current_player = match self.current_player {
Player::Circle => Player::Cross,
Player::Cross => Player::Circle,
Player::None => Player::None,
}
};
return true;
},
Msg::Reset => {
for rank in 0..3 {
for file in 0..3 {
self.grid[rank][file] = Player::None;
}
}
self.reset_zone();
self.current_player = Player::Cross;
return true;
}
}
}
}
impl Renderable<Model> for Model {
fn view(&self) -> Html<Self> {
html! {
<>
<button
onclick=|_event| Msg::Reset,
>
{"Nouvelle partie"}
</button>
<div>
<canvas
id="render_zone",
width={format!("{}", CELLS_SIZE * 3)},
height={format!("{}", CELLS_SIZE * 3)},
onclick=|event| Msg::Play(event),
/>
</div>
<h3>
{format!("Joueur: {}",
match self.current_player {
Player::Circle => "O",
Player::Cross => "X",
_ => "",
}
)}
</h3>
</>
}
}
}
fn main() {
yew::start_app::<Model>();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment