Skip to content

Commit

Permalink
Support win claiming
Browse files Browse the repository at this point in the history
  • Loading branch information
yescallop committed Nov 19, 2024
1 parent 64af07c commit 2e422d0
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 45 deletions.
38 changes: 23 additions & 15 deletions client/src/dialog.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{Confirm, ANALYZE_PREFIX};
use crate::{Confirm, WinClaim, ANALYZE_PREFIX};
use base64::prelude::*;
use c6ol_core::{
game::{Move, Point, Record, Stone},
game::{Move, Record, Stone},
protocol::Request,
};
use leptos::{
Expand All @@ -10,7 +10,6 @@ use leptos::{
prelude::*,
};
use serde::{Deserialize, Serialize};
use tinyvec::ArrayVec;

trait DialogImpl {
type RetVal;
Expand Down Expand Up @@ -252,7 +251,7 @@ pub struct GameMenuDialog {
pub stone: Option<Stone>,
pub online: bool,
pub record: ReadSignal<Record>,
pub tentative_pos: ReadSignal<ArrayVec<[Point; 2]>>,
pub win_claim: ReadSignal<Option<WinClaim>>,
pub requests: ReadSignal<[Option<Stone>; Request::VALUES.len()]>,
}

Expand Down Expand Up @@ -285,7 +284,7 @@ impl DialogImpl for GameMenuDialog {
stone,
online,
record,
tentative_pos,
win_claim,
requests,
} = self;

Expand Down Expand Up @@ -397,20 +396,22 @@ impl DialogImpl for GameMenuDialog {
})}
</div>
<div class="btn-group">
<button value=ret!(ClaimWin) disabled=ended>
<button
class:pushed=move || win_claim.read().is_some()
value=ret!(ClaimWin)
disabled=ended
>
"Claim Win"
</button>
<button
value=ret!(Submit)
disabled=move || ended() || record.read().turn() != stone
disabled=move || {
ended()
|| (record.read().turn() != stone
&& !matches!(win_claim.get(), Some(WinClaim::Ready(..))))
}
>
{move || {
if tentative_pos.read().len() < record.read().max_stones_to_play() {
"Pass"
} else {
"Submit"
}
}}
"Submit"
</button>
</div>
}
Expand Down Expand Up @@ -499,9 +500,16 @@ impl DialogImpl for ConfirmDialog {

let message = match &self.0 {
Confirm::MainMenu => "Back to main menu?",
Confirm::Submit(_, _) => "Submit the move?",
Confirm::Submit(_, None) => "Place one stone?",
Confirm::Submit(_, Some(_)) => "Place two stones?",
Confirm::Pass(None) => "Place no stone and pass?",
Confirm::Pass(Some(_)) => "Place one stone and pass?",
Confirm::Claim(tentative, ..) => match tentative.len() {
// TODO: Inform the user if they're claiming a win for the opponent?
0 => "Claim a win?",
1 => "Place one stone and claim a win?",
_ => "Place two stones and claim a win?",
},
Confirm::Request(req) => match req {
Request::Draw => "Offer a draw?",
Request::Retract => "Request to retract the previous move?",
Expand Down
101 changes: 92 additions & 9 deletions client/src/game_view.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{console_log, Event};
use c6ol_core::game::{Move, Point, Record, Stone};
use crate::{console_log, Event, WinClaim};
use c6ol_core::game::{Direction, Move, Point, Record, Stone};
use leptos::{ev, html, prelude::*};
use std::{
collections::{HashMap, HashSet},
Expand All @@ -14,6 +14,7 @@ use web_sys::{
const BOARD_COLOR: &str = "#ffcc66";
const CURSOR_COLOR_ACTIVE: &str = "darkred";
const CURSOR_COLOR_INACTIVE: &str = "grey";
const WIN_RING_COLOR: &str = "darkred";

const DEFAULT_VIEW_SIZE: i16 = 15;

Expand All @@ -24,6 +25,7 @@ const LINE_DASH_RATIO: f64 = 5.0;

const STONE_RADIUS_RATIO: f64 = 2.25;
const DOT_RADIUS_RATIO: f64 = STONE_RADIUS_RATIO * 6.0;
const WIN_RING_WIDTH_RATIO: f64 = STONE_RADIUS_RATIO * 6.0;

const CURSOR_LINE_WIDTH_RATIO: f64 = 16.0;
const CURSOR_OFFSET_RATIO: f64 = 8.0;
Expand Down Expand Up @@ -213,7 +215,7 @@ fn context_2d(canvas: HtmlCanvasElement) -> CanvasRenderingContext2d {
/// All `Point`s in the props are board positions.
#[component]
pub fn GameView(
record: ReadSignal<Record>,
record: RwSignal<Record>,
stone: ReadSignal<Option<Stone>>,
disabled: impl Fn() -> bool + Send + Sync + 'static,
on_event: impl Fn(Event) + Copy + 'static,
Expand All @@ -229,6 +231,7 @@ pub fn GameView(
#[prop(optional)] cursor_pos: RwSignal<Option<Point>>,
#[prop(optional)] phantom_pos: RwSignal<Option<Point>>,
#[prop(optional)] tentative_pos: RwSignal<ArrayVec<[Point; 2]>>,
#[prop(optional)] win_claim: RwSignal<Option<WinClaim>>,
) -> impl IntoView {
let disabled = Memo::new(move |_| disabled());

Expand Down Expand Up @@ -267,10 +270,49 @@ pub fn GameView(
let phantom = phantom_pos.get();
let mut tentative = tentative_pos.get();

if calc().board_to_view_pos(cursor).is_none()
|| !our_turn()
|| record.read().stone_at(cursor).is_some()
{
if calc().board_to_view_pos(cursor).is_none() {
return;
}

if let Some(claim) = win_claim.get() {
let Some(stone) = stone.get() else {
return;
};

let new_claim = match claim {
WinClaim::PendingPoint | WinClaim::Ready(..) => WinClaim::PendingDirection(cursor),
WinClaim::PendingDirection(p) => {
let dx = cursor.x - p.x;
let dy = cursor.y - p.y;

if dx == 0 && dy == 0 {
WinClaim::PendingPoint
} else if dx == 0 || dy == 0 || dx.abs() == dy.abs() {
let dir = Direction::from_unit_vec(dx.signum(), dy.signum()).unwrap();

if record.write_untracked().with_temp_placements(
stone,
&tentative,
|record| record.test_winning_row(p, dir).is_some(),
) {
WinClaim::Ready(p, dir)
} else {
WinClaim::PendingDirection(cursor)
}
} else {
WinClaim::PendingDirection(cursor)
}
}
};
win_claim.set(Some(new_claim));

if let WinClaim::Ready(..) = new_claim {
on_event(Event::Submit);
}
return;
}

if !our_turn() || record.read().stone_at(cursor).is_some() {
return;
}

Expand Down Expand Up @@ -752,6 +794,25 @@ pub fn GameView(
draw_circle(p, stone_radius);
}

let draw_win_ring = |p: Point| {
let ring_width = grid_size / WIN_RING_WIDTH_RATIO;
ctx.set_line_width(ring_width);

let (x, y) = calc.view_to_canvas_pos(p);
ctx.begin_path();
ctx.arc(x, y, stone_radius - ring_width / 2.0, 0.0, f64::consts::TAU)
.unwrap();
ctx.stroke();
};

let draw_win = |p: Point, dir: Direction| {
for p in iter::once(p).chain(p.adjacent_iter(dir).take(5)) {
if let Some(p) = calc.board_to_view_pos(p) {
draw_win_ring(p);
}
}
};

// Draw the previous move.
if let Some(mov) = record.prev_move() {
let stone = Record::turn_at(move_index - 1);
Expand All @@ -763,7 +824,10 @@ pub fn GameView(
draw_circle(p, dot_radius);
}
}
Move::Win(_, _) => todo!(),
Move::Win(p, dir) => {
ctx.set_stroke_style_str(WIN_RING_COLOR);
draw_win(p, dir);
}
Move::Pass | Move::Draw | Move::Resign(_) => {
let text = match mov {
Move::Pass => "PASS",
Expand Down Expand Up @@ -839,6 +903,23 @@ pub fn GameView(
}
}

// Draw the win claim.
if let Some(claim) = win_claim.get_untracked() {
match claim {
WinClaim::PendingPoint => {}
WinClaim::PendingDirection(p) => {
if let Some(p) = calc.board_to_view_pos(p) {
ctx.set_stroke_style_str("grey");
draw_win_ring(p);
}
}
WinClaim::Ready(p, dir) => {
ctx.set_stroke_style_str("grey");
draw_win(p, dir);
}
}
}

// Draw the cursor.
if let Some(p) = cursor_pos.get().and_then(|p| calc.board_to_view_pos(p)) {
let (x, y) = calc.view_to_canvas_pos(p);
Expand Down Expand Up @@ -874,16 +955,18 @@ pub fn GameView(
record.track();
stone.track();

// Clear phantom and tentative stones if the record or the stone changed.
// Clear phantom, tentatives and win claim if the record or the stone changed.
*phantom_pos.write_untracked() = None;
*tentative_pos.write_untracked() = ArrayVec::new();
*win_claim.write_untracked() = None;

changed.notify();
});

Effect::new(move || {
phantom_pos.track();
tentative_pos.track();
win_claim.track();

changed.notify();
});
Expand Down
Loading

0 comments on commit 2e422d0

Please sign in to comment.