1 module board; 2 3 import core.atomic : atomicStore; 4 import std.concurrency : spawn, thisTid, Tid, send, receive; 5 6 import dlangui; 7 8 import dreversi.agent; 9 import dreversi.env : Board, reset, P = Point, put, pass; 10 11 // helper for scaling relative to average 96dpi FullHD, IDK but maybe a bad idea after all 12 T scaledByDPI(T)(T val) 13 { 14 return val *= (SCREEN_DPI() / cast(T)96); 15 } 16 17 struct CancelMessage {} 18 struct CancelAckMessage {} 19 20 void worker(Tid parent, bool isBlack, int depth, shared(bool)* _agentDone) 21 { 22 auto agent = AlphaBetaAgent(isBlack, true); 23 bool canceled = false; 24 Log.d("agent worker started at ", thisTid); 25 26 while (!canceled) 27 { 28 receive( 29 (immutable Board env) 30 { 31 Log.d("agent received board"); 32 if (pass(env, agent.isBlack)) { 33 Log.d("skip agent turn"); 34 send(parent, env); 35 } else { 36 const action = agent.select(env, depth); 37 immutable next = put(env, agent.isBlack, action.row, action.col); 38 Log.d("agent move: ", [action.row, action.col]); 39 send(parent, next); 40 Log.d(next); 41 } 42 atomicStore(*_agentDone, true); 43 }, 44 (CancelMessage m) 45 { 46 send(parent, CancelAckMessage()); 47 canceled = true; 48 } 49 ); 50 } 51 } 52 53 class BoardWidget : CanvasWidget 54 { 55 const int rows, cols; 56 const int margin; 57 const float lineWidth; 58 const bool isBlack; 59 60 Board board; 61 Tid agentTid; 62 bool _isPlayerTurn; 63 64 shared bool _agentDone = false; 65 66 this(int rows, int cols, int depth, bool playFirst = true) 67 { 68 super("board-widget"); 69 70 this.rows = rows; 71 this.cols = cols; 72 this._isPlayerTurn = playFirst; 73 this.isBlack = playFirst; 74 75 this.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); 76 this.margin = 100.scaledByDPI; 77 this.lineWidth = 3.scaledByDPI; 78 super.minHeight = 400.scaledByDPI; 79 this.clickable(true); 80 this.board = reset(rows, cols); 81 this.agentTid = spawn(&worker, thisTid, !this.isBlack, depth, &this._agentDone); 82 83 this.mouseEvent = delegate bool(Widget w1, MouseEvent e) 84 { 85 if (e.lbutton.isDown) 86 { 87 const rc = xy2rc(e.x, e.y); 88 const move = rc.int2; 89 Log.d("clicked at ", move); 90 if (this._isPlayerTurn) 91 { 92 if (valid(rc)) 93 { 94 immutable next = board.put(this.isBlack, move[1], move[0]); 95 if (next.valid) 96 { 97 this.board = next; 98 this._agentDone.atomicStore(false); 99 this._isPlayerTurn = false; 100 send(this.agentTid, next); 101 } 102 } 103 } 104 } 105 return true; 106 }; 107 } 108 109 ~this() 110 { 111 import std.concurrency : receiveOnly; 112 113 Log.d("waiting for agent thread closed"); 114 send(this.agentTid, CancelMessage()); 115 // receiveOnly!CancelAckMessage; 116 } 117 118 struct RC 119 { 120 float row, col; 121 122 int[2] int2() const { return [cast(int) row, cast(int) col]; } 123 } 124 125 auto valid(RC rc) 126 { 127 return 0 <= rc.row && rc.row < this.rows && 0 <= rc.col && rc.col < this.cols; 128 } 129 130 auto xy2rc(int x, int y) 131 { 132 const rc = shrinkToBoard(this.pos); 133 const width = rc.right - rc.left; 134 const height = rc.bottom - rc.top; 135 const sx = width / this.cols; 136 const sy = height / this.rows; 137 return RC(cast(float) (x - rc.left) / sx, 138 cast(float) (y - rc.top) / sy); 139 } 140 141 override protected void measuredContent( 142 int parentWidth, int parentHeight, 143 int contentWidth, int contentHeight) 144 { 145 import std.algorithm.comparison; 146 _measuredHeight = max(minHeight, min(contentHeight, contentWidth)); 147 _measuredWidth = _measuredHeight; // max(minHeight, contentWidth); 148 } 149 150 protected void drawText(DrawBuf buf, Rect rc, dstring text) 151 { 152 FontRef font = font(); 153 Point sz = font.textSize(text); 154 applyAlign(rc, sz, Align.HCenter, Align.Bottom ); 155 font.drawText(buf, rc.left, rc.top, text, textColor, 4, 0, textFlags); 156 } 157 158 // shrink to square with margin 159 Rect shrinkToBoard(Rect rc) 160 { 161 const _width = rc.right - rc.left; 162 const _height = rc.bottom - rc.top; 163 if (_width > _height) 164 { 165 rc.shrink((_width - height) / 2, 0); 166 } 167 else 168 { 169 rc.shrink(0, (_height - _width) / 2); 170 } 171 rc.shrink(this.margin, this.margin); 172 return rc; 173 } 174 175 override void doDraw(DrawBuf buf, Rect _rc) 176 { 177 if (!this._isPlayerTurn && this._agentDone) 178 179 { 180 receive( 181 (const Board b) { 182 this.board = b; 183 this._isPlayerTurn = true; 184 }); 185 } 186 187 import std.conv : to; 188 189 const rc = shrinkToBoard(_rc); 190 191 // draw background 192 buf.fillRect(rc, 0x119911); 193 194 // draw row/col numbered lines 195 const width = rc.right - rc.left; 196 const height = rc.bottom - rc.top; 197 const sx = cast(float) width / this.cols; 198 const sy = cast(float) height / this.rows; 199 const fy = cast(int) (sy * 0.4); 200 foreach (r; 0 .. this.rows + 1) 201 { 202 const y = rc.top + sy * r + this.lineWidth / 2; 203 if (r < this.rows) 204 { 205 const fontRc = Rect(rc.left - fy, cast(int) y + fy, 206 rc.left, cast(int) y + fy * 2); 207 this.drawText(buf, fontRc, r.to!dstring); 208 } 209 buf.drawLineF(PointF(rc.left, y), 210 PointF(rc.right, y), 211 lineWidth, 212 Color.black); 213 } 214 foreach (c; 0 .. this.cols + 1) 215 { 216 const x = cast(float) rc.left + sx * c + this.lineWidth / 2.0; 217 if (c < this.cols) 218 { 219 const fontRc = Rect(cast(int) x + fy, rc.top + fy, 220 cast(int) x + fy * 2, rc.top); 221 this.drawText(buf, fontRc, c.to!dstring); 222 } 223 buf.drawLineF(PointF(x, rc.top), 224 PointF(x, rc.bottom), 225 lineWidth, 226 Color.black); 227 } 228 229 // draw circles 230 assert(!board.empty, "Error: board is not initialized"); 231 const rmargin = 5.scaledByDPI; 232 const rx = sx / 2 - rmargin; 233 const ry = sy / 2 - rmargin; 234 foreach (r; 0 .. this.rows) 235 { 236 foreach (c; 0 .. this.cols) 237 { 238 auto p = this.board[r, c]; 239 if (p != P.empty) 240 { 241 buf.drawEllipseF(rc.left + c * sx + rx + rmargin, 242 rc.top + r * sy + ry + rmargin, 243 rx, ry, 0, Color.black, 244 p == P.black ? Color.black : Color.white); 245 } 246 } 247 } 248 } 249 }