diff --git a/src/components/farm/Board.jsx b/src/components/farm/Board.jsx
index 9aa9985..24553f3 100644
--- a/src/components/farm/Board.jsx
+++ b/src/components/farm/Board.jsx
@@ -34,7 +34,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser, faUsers, faTractor, faWindowRestore,
faDollarSign, faTimes, faExchangeAlt,
faInfoCircle, faArrowUp, faArrowDown, faAward,
- faBan, faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons'
+ faBan, faArrowCircleLeft, faPlusCircle } from '@fortawesome/free-solid-svg-icons'
import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import SpaceNode from './SpaceNode.jsx'
@@ -48,21 +48,22 @@ import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer,
import { buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit,
buyUncleBert, skip, endAiTurn, startGame, readyToStart,
- leaveGame, kickPlayer, toggleRevealForTrade } from './interface.js'
+ leaveGame, kickPlayer, toggleRevealForTrade,
+ addAIPlayer } from './interface.js'
function netWorth(player) {
return ((player.assets.hay + player.assets.grain) * 2000) +
- (player.assets.fruit * 5000) +
- (player.assets.cows * 500) +
- ((player.assets.harvester + player.assets.tractor) * 10000) +
+ (player.assets.fruit * 5000) +
+ (player.assets.cows * 500) +
+ ((player.assets.harvester + player.assets.tractor) * 10000) +
player.displayCash - player.debt;
}
function assetsValue(player) {
return ((player.assets.hay + player.assets.grain) * 2000) +
- (player.assets.fruit * 5000) +
- (player.assets.cows * 500) +
- ((player.assets.harvester + player.assets.tractor) * 10000);
+ (player.assets.fruit * 5000) +
+ (player.assets.cows * 500) +
+ ((player.assets.harvester + player.assets.tractor) * 10000);
}
class ResourceUnit extends React.Component {
@@ -617,227 +618,227 @@ class TradeContainer extends React.Component {
return (
{this.state.showCards ? (
- c.id).concat(this.state.receiveCards.map(c => c.id))}
- />
+ c.id).concat(this.state.receiveCards.map(c => c.id))}
+ />
) : (
- <>
-
-
-
-
- {Object.keys(player.ridges).map((ridge, idx) => (
-
- {!tradeProposed && (player.ridges[ridge] > 0) && !toTrade[ridge] ? (
-
- ) : (<>>)}
- {' '}
-
- ))}
-
- {this.resources.map((o, i) => {
- let amount = o.key === 'money' ? Math.floor(player.cash / 1000) : player.assets[o.key];
- if (o.key === 'cows') { amount = player.assets.cows - playerRidgeCows(player); }
- return (
-
- {o.icon ? (
- <>
- (K)
-
- >
- ) : ''}
- {amount + Math.min(0, toTrade[o.key])}
- {o.icon ? (
) : ''}
- {!tradeProposed && ((amount + Math.min(0, toTrade[o.key])) > 0 || toTrade[o.key] > 0) ? (
- <>
-
- {o.key === 'money' && (amount + Math.min(0, toTrade[o.key])) >= 10 ? (
-
- ) : ''}
- >
- ) : (<>>)}
-
- );
- })}
-
-
-
-
-
-
- {Object.keys(otherPlayer ? otherPlayer.ridges : []).map((ridge, idx) => (
-
- {(otherPlayer.ridges[ridge] > 0) && toTrade[ridge] ? (
+ <>
+
+
+
+
+ {Object.keys(player.ridges).map((ridge, idx) => (
+
+ {!tradeProposed && (player.ridges[ridge] > 0) && !toTrade[ridge] ? (
+
+ ) : (<>>)}
+ {' '}
+
+ ))}
+
+ {this.resources.map((o, i) => {
+ let amount = o.key === 'money' ? Math.floor(player.cash / 1000) : player.assets[o.key];
+ if (o.key === 'cows') { amount = player.assets.cows - playerRidgeCows(player); }
+ return (
+
+ {o.icon ? (
+ <>
+ (K)
+
+ >
+ ) : ''}
+ {amount + Math.min(0, toTrade[o.key])}
+ {o.icon ? (
) : ''}
+ {!tradeProposed && ((amount + Math.min(0, toTrade[o.key])) > 0 || toTrade[o.key] > 0) ? (
+ <>
+
+ {o.key === 'money' && (amount + Math.min(0, toTrade[o.key])) >= 10 ? (
+
+ ) : ''}
+ >
+ ) : (<>>)}
+
+ );
+ })}
+
+
+
+
+
+
+ {Object.keys(otherPlayer ? otherPlayer.ridges : []).map((ridge, idx) => (
+
+ {(otherPlayer.ridges[ridge] > 0) && toTrade[ridge] ? (
+
+ ) : (<>>)}
+ {' '}
+
+ ))}
+ {this.state.receiveCards.map((card, idx) => (
- ) : (<>>)}
- {' '}
-
- ))}
- {this.state.receiveCards.map((card, idx) => (
-
- ))}
-
-
-
-
-
- {this.resources.map((o, i) => (
-
- {Math.abs(toTrade[o.key])}
-
- ))}
-
-
-
-
-
- {Object.keys(player.ridges).map((ridge, idx) => (
-
- {(player.ridges[ridge] > 0) && toTrade[ridge] ? (
+ ))}
+
+
+
+
+
+ {this.resources.map((o, i) => (
+
+ {Math.abs(toTrade[o.key])}
+
+ ))}
+
+
+
+
+
+ {Object.keys(player.ridges).map((ridge, idx) => (
+
+ {(player.ridges[ridge] > 0) && toTrade[ridge] ? (
+
+ ) : (<>>)}
+ {' '}
+
+ ))}
+ {this.state.sendCards.map((card, idx) => (
- ) : (<>>)}
- {' '}
-
- ))}
- {this.state.sendCards.map((card, idx) => (
-
- ))}
-
-
-
-
-
- {otherPlayer ? (
-
- {this.resources.map((o, i) => {
- let amount = o.key === 'money' ? 99 : otherPlayer.assets[o.key];
- if (o.key === 'cows') { amount = otherPlayer.assets.cows - playerRidgeCows(otherPlayer); }
- return (
-
- {o.icon ? (
- <>
- (K)
-
- >
- ) : ''}
- {amount - Math.max(0, toTrade[o.key])}
- {o.icon ? (
) : ''}
- {!tradeProposed && ((amount - Math.max(0, toTrade[o.key])) > 0 || toTrade[o.key] < 0) ? (
- <>
-
- {o.key === 'money' && (amount - Math.min(0, toTrade[o.key])) >= 10 ? (
-
+ ))}
+
+
+
+
+
+ {otherPlayer ? (
+
+ {this.resources.map((o, i) => {
+ let amount = o.key === 'money' ? 99 : otherPlayer.assets[o.key];
+ if (o.key === 'cows') { amount = otherPlayer.assets.cows - playerRidgeCows(otherPlayer); }
+ return (
+
+ {o.icon ? (
+ <>
+ (K)
+
+ >
) : ''}
- >
- ) : (<>>)}
-
- );
- })}
+ {amount - Math.max(0, toTrade[o.key])}
+ {o.icon ? (
) : ''}
+ {!tradeProposed && ((amount - Math.max(0, toTrade[o.key])) > 0 || toTrade[o.key] < 0) ? (
+ <>
+
+ {o.key === 'money' && (amount - Math.min(0, toTrade[o.key])) >= 10 ? (
+
+ ) : ''}
+ >
+ ) : (<>>)}
+
+ );
+ })}
+
+ ) : (<>>)}
+ {Object.keys(otherPlayer ? otherPlayer.ridges : []).map((ridge, idx) => (
+
+ {!tradeProposed && ((otherPlayer.ridges[ridge] > 0) && !toTrade[ridge]) ? (
+
+ ) : (<>>)}
+ {' '}
+
+ ))}
+
+ {otherPlayer && otherPlayer.revealedCards.length > 0 ? (
+
this.viewPlayerCards(e, otherPlayer)}>
+ Trade Cards
+
+ ) : (<>>)}
- ) : (<>>)}
- {Object.keys(otherPlayer ? otherPlayer.ridges : []).map((ridge, idx) => (
-
- {!tradeProposed && ((otherPlayer.ridges[ridge] > 0) && !toTrade[ridge]) ? (
-
- ) : (<>>)}
- {' '}
-
- ))}
-
- {otherPlayer && otherPlayer.revealedCards.length > 0 ? (
- this.viewPlayerCards(e, otherPlayer)}>
- Trade Cards
-
- ) : (<>>)}
-
-
-
-
-
- {player.trade.error ? (
- ERROR: {player.trade.error}
- ) : (<>>)}
- {tradeProposed && player.trade.originator === player.name ? (
-
- ) : (<>>)}
- {tradeProposed && player.trade.originator !== player.name ? (
- <>
- {' '}
-
- >
- ) : (<>>)}
- {!tradeProposed ? (
-
- ) : (<>>)}
-
-
- >)}
+
+
+
+
+ {player.trade.error ? (
+ ERROR: {player.trade.error}
+ ) : (<>>)}
+ {tradeProposed && player.trade.originator === player.name ? (
+
+ ) : (<>>)}
+ {tradeProposed && player.trade.originator !== player.name ? (
+ <>
+ {' '}
+
+ >
+ ) : (<>>)}
+ {!tradeProposed ? (
+
+ ) : (<>>)}
+
+
+ >)}
);
}
@@ -1340,7 +1341,7 @@ class Harvest extends React.Component {
Get ready to harvest
{this.props.acres}
{this.props.crop === 'cows' ? ' head of cow' : ' acres'}!
- {isCurrentPlayer ? (
+ {isCurrentPlayer || this.props.currentPlayer.ai ? (
@@ -1356,7 +1357,7 @@ class Harvest extends React.Component {
view = ( this.nextView('income')}
- skip={this.props.player.name === this.props.game.currentPlayer}
+ skip={this.props.player.name === this.props.game.currentPlayer || this.props.currentPlayer.ai}
autoSkip={this.props.autoSkip === 'die'}
showScreenDelay={2000} />);
break;
@@ -1372,7 +1373,7 @@ class Harvest extends React.Component {
- {isCurrentPlayer ? (
+ {isCurrentPlayer || this.props.currentPlayer.ai ? (
@@ -1389,7 +1390,7 @@ class Harvest extends React.Component {
dangerouslySetInnerHTML={{__html: this.props.contents}} />
- {isCurrentPlayer ? (
+ {isCurrentPlayer || this.props.currentPlayer.ai ? (
@@ -1424,7 +1425,7 @@ class Harvest extends React.Component {
- {isCurrentPlayer ?
+ {isCurrentPlayer || this.props.currentPlayer.ai ?
(
@@ -1488,7 +1489,7 @@ class Action extends React.Component {
bertSubmit = (e) => {
e.preventDefault();
- if (this.state.bertChoice === 'accept') {
+ if (this.state.bertChoice === 'accept' || this.props.currentPlayer.ai) {
buyUncleBert();
this.props.showNextAction();
} else if (this.state.bertChoice === 'deny') {
@@ -1550,6 +1551,11 @@ class Action extends React.Component {
>
);
+ const aiButton = (
+
+ );
view = (
@@ -1567,6 +1573,7 @@ class Action extends React.Component {
{(this.props.player.name === this.props.game.currentPlayer) ?
ffButtons : ()}
+ {currentPlayer.ai ? aiButton : (<>>)}
@@ -1937,6 +1944,11 @@ class StartGame extends React.Component {
): (<>>)}
))}
+
+
+ AI
+
+
Game Settings
diff --git a/src/components/farm/interface.js b/src/components/farm/interface.js
index e4c3725..b297540 100644
--- a/src/components/farm/interface.js
+++ b/src/components/farm/interface.js
@@ -31,7 +31,8 @@ import { itemCard, fateCard } from 'game.js'
export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, handleMessage,
nextAction, buyUncleBert, actionsFinished, skip, endAiTurn,
- startGame, readyToStart, leaveGame, kickPlayer, toggleRevealForTrade }
+ startGame, readyToStart, leaveGame, kickPlayer, toggleRevealForTrade,
+ addAIPlayer }
let store;
let movingTimer = 0;
@@ -217,6 +218,10 @@ function kickPlayer(name) {
sendCommand({ type: 'kick-player', name });
}
+function addAIPlayer() {
+ sendCommand({ type: 'add-ai-player' });
+}
+
function toggleRevealForTrade(id) {
sendCommand({ type: 'toggle-reveal-for-trading', id });
}
diff --git a/src/server/farm.scm b/src/server/farm.scm
index dff324f..9f6d9a0 100644
--- a/src/server/farm.scm
+++ b/src/server/farm.scm
@@ -120,7 +120,7 @@
(next-year-rules initform: '() accessor: player-next-year-rules)
(color initform: #f accessor: player-color)
(name initform: "PLAYER X" accessor: player-name)
- (user-id initform: #f accessor: player-user-id)
+ (user-id initform: -1 accessor: player-user-id)
(trade initform: '() accessor: player-trade)
(last-updated initform: 0 accessor: player-last-updated)
(last-cash initform: 5000 accessor: player-last-cash)
@@ -202,7 +202,8 @@
(last-cash . ,(player-cash player))
(hay-doubled . ,(player-hay-doubled player))
(corn-doubled . ,(player-corn-doubled player))
- (stats . ,(player-stats player))))
+ (stats . ,(player-stats player))
+ (ai . ,(ai-player? player))))
(define (game->sexp g)
`((id . ,(game-id g))
@@ -317,7 +318,7 @@
(set! *app* (sexp->app (read))))))
(define (sexp->player x)
- (let ((p (apply make
+ (let ((p (apply make (if (alist-ref 'ai x) )
'farmers-fates (let ((ffs (alist-ref 'farmers-fates x)))
(list-copy
(filter (lambda (card)
@@ -422,7 +423,7 @@
(define (add-ai-to-game game color name)
(let ((player (make
- 'cash 10000
+ 'cash (game-setting 'starting-cash game)
'display-cash (game-setting 'starting-cash game)
'debt (game-setting 'starting-debt game)
'color color
@@ -555,6 +556,7 @@
(state . ,(symbol->string (player-state p)))
(cards . ,(list->vector (append (player-farmers-fates p)
(player-otbs p))))
+ (revealedCards . ,(list->vector (player-revealed-cards p)))
(color . ,(symbol->string (player-color p)))
(name . ,(player-name p))
(user-id . ,(player-user-id p))
@@ -1044,6 +1046,10 @@
game-in-memory
(let ((db-game (sexp->game (db-fetch-game id))))
(push! db-game (app-games *app*))
+ (for-each (lambda (p)
+ (when (ai-player? p)
+ (thread-start! (make-ai-push-receiver db-game p))))
+ (game-players db-game))
db-game))))
(define (next-roll last-roll)
@@ -1261,9 +1267,13 @@
((and (string=? type "next-action")
(ai-player? (game-current-player game)))
(print "ai next action trigger")
- (print (player-name (game-current-player game)))
(message-players! game player '() type: "ai-next-action")
(create-ws-response player "update" `()))
+ ((and (string=? type "buy-uncle-bert")
+ (ai-player? (game-current-player game)))
+ (print "ai uncle bert trigger")
+ (message-players! game player '() type: "ai-uncle-bert")
+ (create-ws-response player "update" `()))
((string=? type "end-ai-turn")
(message-players! game player '() type: "end-ai-turn")
(create-ws-response player "update" `()))
@@ -1469,6 +1479,21 @@
(set-startup-otbs game player (alist-ref 'starting-otbs (game-settings game)))
(message-players! game player '() type: "update")
(create-start-response "new-game-started")))
+ ((string=? type "add-ai-player")
+ (let* ((user (fetch-user-by-id (session-ref (sid) 'user-id)))
+ (name (conc "AI Player "
+ (+ 1 (length (filter ai-player? (game-players game))))))
+ (game (*game*))
+ (color (car (game-colors game)))
+ (player (add-ai-to-game game
+ color
+ name)))
+ (safe-set! (game-colors game) (filter (cut neq? <> color) (game-colors game)))
+ (set-startup-otbs game player (alist-ref 'starting-otbs (game-settings game)))
+ (safe-set! (player-ready-to-start player) #t)
+ (thread-start! (make-ai-push-receiver game player))
+ (message-players! game player '() type: "update")
+ (create-ws-response (*player*) "update" '())))
((string=? type "join-as-existing")
(let* ((id (or (alist-ref 'gameId msg)
(session-ref (sid) 'game-id)))
@@ -1547,6 +1572,79 @@
(message-players! (*game*) (*player*) '() type: "update")
(create-ws-response (*player*) "update" '()))))
+(define (round-down-1000 val)
+ (- val (remainder val 1000)))
+
+(define (ai-buy player game)
+ (print "ai attempting to buy")
+ (let ((room (+ (- (game-setting 'max-debt game) (player-debt player)) (round-down-1000 (player-cash player))))
+ (crops (map (lambda (card)
+ (string->symbol (alist-ref 'crop card)))
+ (player-otbs player))))
+ (print (conc "room: " room))
+ (print (conc "crops: " crops))
+ (let ((to-buy
+ (cond ((and (member 'cows crops) (>= room 5000))
+ '(cows 10 5000))
+ ((and (member 'fruit crops) (>= room 25000))
+ '(fruit 5 25000))
+ ((and (member 'grain crops) (>= room 20000))
+ '(grain 10 20000))
+ ((and (member 'hay crops) (>= room 20000))
+ '(hay 10 20000))
+ ((and (member 'harvester crops) (>= room 10000)
+ (= (player-asset 'harvester player) 0))
+ '(harvester 1 10000))
+ ((and (member 'tractor crops) (>= room 10000)
+ (= (player-asset 'tractor player) 0))
+ '(tractor 1 10000))
+ ((and (member 'ridge4 crops) (>= room 50000)
+ (not (find (lambda (p)
+ (> (player-asset 'ridge4 p) 0))
+ (game-players game))))
+ '(ridge4 50 50000))
+ ((and (member 'ridge3 crops) (>= room 40000)
+ (not (find (lambda (p)
+ (> (player-asset 'ridge3 p) 0))
+ (game-players game))))
+ '(ridge3 40 40000))
+ ((and (member 'ridge2 crops) (>= room 30000)
+ (not (find (lambda (p)
+ (> (player-asset 'ridge2 p) 0))
+ (game-players game))))
+ '(ridge2 30 30000))
+ ((and (member 'ridge1 crops) (>= room 20000)
+ (not (find (lambda (p)
+ (> (player-asset 'ridge1 p) 0))
+ (game-players game))))
+ '(ridge1 20 20000))
+ (else #f))))
+ (print "to buy: " to-buy)
+ (if to-buy
+ (begin
+ (print (conc "buying crop: " (first to-buy)))
+ (if (eq? (buy-crop (normalize-crop (first to-buy))
+ (first to-buy)
+ (second to-buy)
+ (min (third to-buy) (round-down-1000 (player-cash player)))
+ player
+ game)
+ #t)
+ (let ((id (alist-ref 'id
+ (find (lambda (c) (equal? (alist-ref 'crop c) (symbol->string (first to-buy))))
+ (player-otbs player)))))
+ (safe-set! (game-otbs game)
+ (append (game-otbs game)
+ (filter (lambda (x) (= id (alist-ref 'id x)))
+ (player-otbs player))))
+ (safe-set! (player-otbs player)
+ (filter (lambda (x) (not (= id (alist-ref 'id x))))
+ (player-otbs player)))
+ #t)
+ #f))
+ #f)))
+ )
+
(define (process-ai-push-message player game msg)
(print (player-name player))
(print msg)
@@ -1555,47 +1653,55 @@
(if (and (eq? (player-state player) 'pre-turn)
(not (ai-processing-turn player)))
(begin (set! (ai-processing-turn player) #t)
+ ;; time to buy
+ (when (and (>= (player-space player) 9) (<= (player-space player) 14))
+ (let loop ((cont (ai-buy player game)))
+ (when cont (loop (ai-buy player game)))))
(let ((res (process-message player game "roll" '((type . "roll")))))
- (print "rolled a " (alist-ref 'value res))
- ;; (process-message player game "next-action" '((type . "next-action")))
- ;; (let loop ((msg (process-message player game "next-action" '((type . "next-action")))))
- ;; (if (alist-ref 'action msg)
- ;; (loop (process-message player game "next-action" '((type . "next-action"))))
- ;; (print "done with actions")))
- ))))
+ (print "rolled a " (alist-ref 'value res))))))
((auto-skip)
- (print "ai auto-skip")
- ;; (when (ai-processing-turn player)
- ;; (process-message player game "next-action" '((type . "next-action"))))
- )
+ (print "ai auto-skip"))
((ai-next-action)
(print "ai-next-action")
(when (ai-processing-turn player)
(let ((res (process-message player game "next-action" '((type . "next-action")))))
- (display "res: ")
- (write res)
- (newline)
- ;; (print "res1: " (eq? (alist-ref 'event res) 'action))
- ;; (print "res2: " (not (alist-ref 'action res)))
- ;; (print "res3: " (and (eq? (alist-ref 'event res) 'action)
- ;; (not (alist-ref 'action res))))
- ;; (when (and (string=? (alist-ref 'event res) "action")
- ;; (not (alist-ref 'action res)))
- ;; (print "ending turn")
- ;; (thread-sleep! 0.5)
- ;; (set! (ai-processing-turn player) #f)
- ;; (process-message player game "turn-ended" '()))
+ res
+ ;; (display "res: ")
+ ;; (write res)
+ ;; (newline)
)))
- ((end-ai-turn)
+ ((ai-uncle-bert)
+ (print "ai-uncle-bert")
(when (ai-processing-turn player)
- (print "ending turn")
- (thread-sleep! 0.5)
- (set! (ai-processing-turn player) #f)
- (process-message player game "turn-ended" '())
- ))))
+ (safe-set! (player-debt player) (+ (player-debt player) 10000))
+ (safe-set! (player-assets player)
+ (alist-update 'hay (+ (alist-ref 'hay (player-assets player)) 10)
+ (player-assets player)))))
+ ((end-ai-turn)
+ (if (eq? (player-state player) 'pre-turn)
+ (process-ai-push-message player game '((type . "update"))) ;; restarting at AI player's turn
+ (when (ai-processing-turn player)
+ (when (< (player-cash player) 0)
+ (print "taking out loan")
+ (process-message player game "loan" `((amount . ,(/ (+ (abs (player-cash player))
+ (remainder (abs (player-cash player)) 1000))
+ 1000)))))
+ (when (>= (player-cash player) 1000)
+ (print "repaying loan")
+ (process-message player game "loan" `((amount . ,(* (/ (- (player-cash player)
+ (remainder (player-cash player) 1000))
+ 1000)
+ -1)))))
+ (print "ending turn")
+ ;; (thread-sleep! 0.5)
+ (set! (ai-processing-turn player) #f)
+ (process-message player game "turn-ended" '())
+ )))))
(define (make-ai-push-receiver game player)
(lambda ()
+ (*game* game)
+ (*player* player)
(let loop ((msg (mailbox-receive! (player-mailbox player))))
(process-ai-push-message player game msg)
(loop (mailbox-receive! (player-mailbox player))))))