|
|
Line 1: |
Line 1: |
− | This tutorial gives a brief overview of what to do to get a small, graphical Minesweeper game running on your Overo. If you have not done so, please go through the tutorials [[Eclipse on Gumstix for new users]] and [[HelloWorld in Java]].
| + | What are the requirements to become a therapist? |
| | | |
− | This does not explain the game of MineSweeper, rather just lets use utilize the code to see an example of the graphical possibilities using an Overo COM.
| |
| | | |
− | *Create a new Project in Eclipse
| + | You can get your training at a community college,shanghai escort professional vocation schools, or dedicated institution. States have various regulations for operating a massage therapy establishment, including passing a national certification exam; you also need to meet cleanliness standards, such as using clean linens and disinfectant. |
− | :*'''File -> New Project -> Java Project'''
| + | |
− | *Create a class '''Main Class'''
| + | |
− | :*Copy and paste the following code to that class:
| + | |
− | <pre>
| + | |
− | import java.awt.BorderLayout;
| + | |
− | import java.awt.Color;
| + | |
− | import java.awt.event.ActionEvent;
| + | |
− | import java.awt.event.ActionListener;
| + | |
| | | |
− | import javax.swing.JButton;
| + | What do you think of those vibrating massage chairs at the malls or the walk-in massage places? |
− | import javax.swing.JColorChooser;
| + | It's a good quick fix to get a knot worked out or problem area that's stretched. You can get pretty kfrldbet relaxed in the course of 15 minutes. I've done it myself - even gone into Brookstone just to sit in the massage chairs. |
− | import javax.swing.JFrame;
| + | |
− | import javax.swing.JOptionPane;
| + | |
| | | |
− | public class MainClass {
| + | You get massages yourself, then? |
− | public static void main(String args[]) {
| + | Absolutely. I trade with colleagues for their services. I'm very fussy - I look for someone who has an attunement to the amount of pressure, speed of strokes, and a certain quality of touch. |
− | JFrame f = new JFrame("JColorChooser Sample");
| + | |
− | f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
| + | |
− | final JButton button = new JButton("Pick to Change Background");
| + | |
| | | |
− | ActionListener actionListener = new ActionListener() {
| + | How do you convince someone who doesn't believe in body work? |
− | public void actionPerformed(ActionEvent actionEvent) {
| + | I don't spend a lot of time trying to sway people. Either you're ready for massage in shanghai therapy or you're not. But it's definitely a feel-good option for many. |
− | Color initialBackground = button.getBackground();
| + | kfrldbet |
− | Color background = JColorChooser.showDialog(null,
| + | |
− | "JColorChooser Sample", initialBackground);
| + | |
− | if (background != null) {
| + | |
− | button.setBackground(background);
| + | |
− | }
| + | |
− | }
| + | |
− | };
| + | |
− | button.addActionListener(actionListener);
| + | |
− | f.add(button, BorderLayout.CENTER);
| + | |
− | f.setSize(300, 200);
| + | |
− | f.setVisible(true);
| + | |
− | }
| + | |
| | | |
− | }</pre>
| + | [http://www.sh-massage.org shanghai massage] |
− | *Create a class '''MineSweeper''' (Capital S!)
| + | [http://www.sh-massage.org massage shanghai] |
− | :*Copy and paste the following code to that class:
| + | [http://www.sh-massage.org massage in shanghai] |
− | <pre>import java.awt.AWTEvent;
| + | [http://www.sh-massage.org shanghai escort] |
− | import java.awt.BorderLayout;
| + | [http://www.theshenzhen.net shenzhen massage] |
− | import java.awt.Color;
| + | [http://www.theshenzhen.net massage in shenzhen] |
− | import java.awt.Component;
| + | [http://www.theshenzhen.net escort shenzhen] |
− | import java.awt.FlowLayout;
| + | [http://www.shanghai-massage-in-shanghai.net shanghai escort] |
− | import java.awt.GridLayout;
| + | [http://www.dongguanescort.net dongguan massage] |
− | import java.awt.Insets;
| + | [http://www.dongguanescort.net massage dongguan] |
− | import java.awt.Point;
| + | [http://www.dongguanescort.net massage in dongguan] |
− | import java.awt.Toolkit;
| + | [http://www.massage-shenzhen.cn shenzhen escort] |
− | import java.awt.event.AWTEventListener;
| + | |
− | import java.awt.event.ActionEvent;
| + | |
− | import java.awt.event.ActionListener;
| + | |
− | import java.awt.event.KeyEvent;
| + | |
− | import java.awt.event.MouseAdapter;
| + | |
− | import java.awt.event.MouseEvent;
| + | |
− | import java.util.ArrayList;
| + | |
− | import java.util.List;
| + | |
− | import java.util.Random;
| + | |
− | | + | |
− | import javax.swing.JButton;
| + | |
− | import javax.swing.JFrame;
| + | |
− | import javax.swing.JLabel;
| + | |
− | import javax.swing.JOptionPane;
| + | |
− | import javax.swing.JPanel;
| + | |
− | | + | |
− | public class MineSweeper extends JPanel implements AWTEventListener, ActionListener {
| + | |
− | | + | |
− | public static enum State {
| + | |
− | Clicked, Marked, Initial, WrongMarked
| + | |
− | }
| + | |
− | | + | |
− | public static enum GameState {
| + | |
− | NotStarted, Playing, Finished
| + | |
− | }
| + | |
− | | + | |
− | private static final int MAX_BOMB_COUNT = 10;
| + | |
− | private int ROWS = 9, COLUMNS = 9, TOTAL = ROWS * COLUMNS;
| + | |
− | private JPanel pnlMain = new JPanel(new GridLayout(ROWS, COLUMNS));
| + | |
− | private JLabel lblBombCount = new JLabel(MAX_BOMB_COUNT + "");
| + | |
− | private JLabel lblTimer = new JLabel("0");
| + | |
− | private boolean isColorCheatOn = false;
| + | |
− | private JButton btnReset = new JButton("Reset");
| + | |
− | | + | |
− | private void startThread() {
| + | |
− | Thread th = new Thread(new Runnable() {
| + | |
− | public void run() {
| + | |
− | while (state == GameState.Playing) {
| + | |
− | lblTimer.setText((Long.parseLong(lblTimer.getText()) + 1) + "");
| + | |
− | lblTimer.updateUI();
| + | |
− | try {
| + | |
− | Thread.sleep(1000);
| + | |
− | } catch (InterruptedException e) {
| + | |
− | e.printStackTrace();
| + | |
− | }
| + | |
− | }
| + | |
− | }
| + | |
− | });
| + | |
− | th.start();
| + | |
− | }
| + | |
− | | + | |
− | private GameState state = GameState.NotStarted;
| + | |
− | | + | |
− | public MineSweeper() {
| + | |
− | setLayout(new BorderLayout());
| + | |
− | add(pnlMain, BorderLayout.CENTER);
| + | |
− | createButtons();
| + | |
− | addControlPanel();
| + | |
− | Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
| + | |
− | }
| + | |
− | | + | |
− | private void showAbout() {
| + | |
− | JOptionPane.showMessageDialog(this, "<html>Author : Justin Cowles Cowperthwaite <br>Version : 1.0</html>", "About", JOptionPane.INFORMATION_MESSAGE);
| + | |
− | }
| + | |
− | | + | |
− | private void restartGame() {
| + | |
− | state = GameState.NotStarted;
| + | |
− | lblTimer.setText("0");
| + | |
− | pnlMain.removeAll();
| + | |
− | createButtons();
| + | |
− | pnlMain.updateUI();
| + | |
− | lblBombCount.setText("" + MAX_BOMB_COUNT);
| + | |
− | lblBombCount.updateUI();
| + | |
− | }
| + | |
− | | + | |
− | private void addControlPanel() {
| + | |
− | JPanel pnlTimer = new JPanel(new FlowLayout(FlowLayout.RIGHT));
| + | |
− | | + | |
− | pnlTimer.add(lblTimer);
| + | |
− | | + | |
− | JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
| + | |
− | | + | |
− | btnReset.setToolTipText("<HTML>Press <B>F2</B> to reset the current game</HTML>");
| + | |
− | pnl.add(lblBombCount);
| + | |
− | pnl.add(btnReset);
| + | |
− | JPanel pnlN = new JPanel(new GridLayout(1, 3));
| + | |
− | | + | |
− | pnlN.add(lblBombCount);
| + | |
− | pnlN.add(pnl);
| + | |
− | pnlN.add(pnlTimer);
| + | |
− | add(pnlN, BorderLayout.NORTH);
| + | |
− | btnReset.addActionListener(this);
| + | |
− | }
| + | |
− | | + | |
− | private void createButtons() {
| + | |
− | List<Point> lstBombsLocation = new ArrayList<Point>();
| + | |
− | | + | |
− | for (int row = 0; row < ROWS; row++) {
| + | |
− | for (int col = 0; col < COLUMNS; col++) {
| + | |
− | JButton btn = getButton(lstBombsLocation, TOTAL, new Point(row, col) {
| + | |
− | @Override
| + | |
− | public String toString() {
| + | |
− | return (int) getX() + ", " + (int) getY();
| + | |
− | }
| + | |
− | | + | |
− | @Override
| + | |
− | public boolean equals(Object obj) {
| + | |
− | return ((Point) obj).getX() == getX() && ((Point) obj).getY() == getY();
| + | |
− | }
| + | |
− | });
| + | |
− | pnlMain.add(btn);
| + | |
− | }
| + | |
− | }
| + | |
− | while (lstBombsLocation.size() < MAX_BOMB_COUNT) {
| + | |
− | updateBomds(lstBombsLocation, pnlMain.getComponents());
| + | |
− | }
| + | |
− | for (Component c : pnlMain.getComponents()) {
| + | |
− | updateBombCount((GameButton) c, pnlMain.getComponents());
| + | |
− | }
| + | |
− | // System.out.println("Total Bomb Count: " + lstBombsLocation.size());
| + | |
− | }
| + | |
− | | + | |
− | private void updateBomds(List<Point> lstBombsLocation, Component[] components) {
| + | |
− | // int currentPosition = new Double(((location.x) * COLUMNS ) +
| + | |
− | // location.getY()).intValue();
| + | |
− | Random r = new Random();
| + | |
− | for (Component c : components) {
| + | |
− | Point location = ((GameButton) c).getPosition();
| + | |
− | int currentPosition = new Double(((location.x) * COLUMNS) + location.getY()).intValue();
| + | |
− | int bombLocation = r.nextInt(TOTAL);
| + | |
− | if (bombLocation == currentPosition) {
| + | |
− | ((GameButton) c).setBomb(true);
| + | |
− | lstBombsLocation.add(((GameButton) c).getPosition());
| + | |
− | return;
| + | |
− | }
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | private GameButton getButton(List<Point> lstBombsLocation, int totalLocations, Point location) {
| + | |
− | GameButton btn = new GameButton(location);
| + | |
− | btn.setMargin(new Insets(0, 0, 0, 0));
| + | |
− | btn.setFocusable(false);
| + | |
− | if (lstBombsLocation.size() < MAX_BOMB_COUNT) {
| + | |
− | if (isBomb()) {
| + | |
− | btn.setBomb(true);
| + | |
− | lstBombsLocation.add(location);
| + | |
− | }
| + | |
− | }
| + | |
− | btn.addMouseListener(new MouseAdapter() {
| + | |
− | @Override
| + | |
− | public void mouseClicked(MouseEvent mouseEvent) {
| + | |
− | if (state != GameState.Playing) {
| + | |
− | state = GameState.Playing;
| + | |
− | startThread();
| + | |
− | }
| + | |
− | if (((GameButton) mouseEvent.getSource()).isEnabled() == false) {
| + | |
− | return;
| + | |
− | }
| + | |
− | if (mouseEvent.getButton() == MouseEvent.BUTTON1) {
| + | |
− | if (((GameButton) mouseEvent.getSource()).getState() == State.Marked) {
| + | |
− | ((GameButton) mouseEvent.getSource()).setState(State.Initial);
| + | |
− | lblBombCount.setText((Long.parseLong(lblBombCount.getText()) + 1) + "");
| + | |
− | ((GameButton) mouseEvent.getSource()).updateUI();
| + | |
− | return;
| + | |
− | }
| + | |
− | ((GameButton) mouseEvent.getSource()).setState(State.Clicked);
| + | |
− | if (((GameButton) mouseEvent.getSource()).isBomb()) {
| + | |
− | blastBombs();
| + | |
− | return;
| + | |
− | } else {
| + | |
− | if (((GameButton) mouseEvent.getSource()).getBombCount() == 0) {
| + | |
− | updateSurroundingZeros(((GameButton) mouseEvent.getSource()).getPosition());
| + | |
− | }
| + | |
− | }
| + | |
− | if (!checkGameState()) {
| + | |
− | ((GameButton) mouseEvent.getSource()).setEnabled(false);
| + | |
− | }
| + | |
− | } else if (mouseEvent.getButton() == MouseEvent.BUTTON3) {
| + | |
− | if (((GameButton) mouseEvent.getSource()).getState() == State.Marked) {
| + | |
− | ((GameButton) mouseEvent.getSource()).setState(State.Initial);
| + | |
− | lblBombCount.setText((Long.parseLong(lblBombCount.getText()) + 1) + "");
| + | |
− | } else {
| + | |
− | ((GameButton) mouseEvent.getSource()).setState(State.Marked);
| + | |
− | lblBombCount.setText((Long.parseLong(lblBombCount.getText()) - 1) + "");
| + | |
− | }
| + | |
− | }
| + | |
− | ((GameButton) mouseEvent.getSource()).updateUI();
| + | |
− | }
| + | |
− | });
| + | |
− | return btn;
| + | |
− | }
| + | |
− | | + | |
− | private boolean checkGameState() {
| + | |
− | boolean isWin = false;
| + | |
− | for (Component c : pnlMain.getComponents()) {
| + | |
− | GameButton b = (GameButton) c;
| + | |
− | if (b.getState() != State.Clicked) {
| + | |
− | if (b.isBomb()) {
| + | |
− | isWin = true;
| + | |
− | } else {
| + | |
− | return false;
| + | |
− | }
| + | |
− | }
| + | |
− | }
| + | |
− | if (isWin) {
| + | |
− | state = GameState.Finished;
| + | |
− | for (Component c : pnlMain.getComponents()) {
| + | |
− | GameButton b = (GameButton) c;
| + | |
− | if (b.isBomb()) {
| + | |
− | b.setState(State.Marked);
| + | |
− | }
| + | |
− | b.setEnabled(false);
| + | |
− | | + | |
− | }
| + | |
− | JOptionPane.showMessageDialog(this, "You win the game :D", "Congrats", JOptionPane.INFORMATION_MESSAGE, null);
| + | |
− | }
| + | |
− | return isWin;
| + | |
− | }
| + | |
− | | + | |
− | private void updateSurroundingZeros(Point currentPoint) {
| + | |
− | Point[] points = getSurroundings(currentPoint);
| + | |
− | | + | |
− | for (Point p : points) {
| + | |
− | GameButton b = getButtonAt(pnlMain.getComponents(), p);
| + | |
− | if (b != null && b.getBombCount() == 0 && b.getState() != State.Clicked && b.getState() != State.Marked && b.isBomb() == false) {
| + | |
− | b.setState(State.Clicked);
| + | |
− | updateSurroundingZeros(b.getPosition());
| + | |
− | b.updateUI();
| + | |
− | }
| + | |
− | if (b != null && b.getBombCount() > 0 && b.getState() != State.Clicked && b.getState() != State.Marked && b.isBomb() == false) {
| + | |
− | b.setEnabled(false);
| + | |
− | b.setState(State.Clicked);
| + | |
− | b.updateUI();
| + | |
− | }
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | private void blastBombs() {
| + | |
− | int blastCount = 0;
| + | |
− | for (Component c : pnlMain.getComponents()) {
| + | |
− | ((GameButton) c).setEnabled(false);
| + | |
− | ((GameButton) c).transferFocus();
| + | |
− | if (((GameButton) c).isBomb() && ((GameButton) c).getState() != State.Marked) {
| + | |
− | ((GameButton) c).setState(State.Clicked);
| + | |
− | ((GameButton) c).updateUI();
| + | |
− | blastCount++;
| + | |
− | }
| + | |
− | if (((GameButton) c).isBomb() == false && ((GameButton) c).getState() == State.Marked) {
| + | |
− | ((GameButton) c).setState(State.WrongMarked);
| + | |
− | }
| + | |
− | }
| + | |
− | lblBombCount.setText("" + blastCount);
| + | |
− | lblBombCount.updateUI();
| + | |
− | state = GameState.Finished;
| + | |
− | JOptionPane.showMessageDialog(this, "You loose the game :(", "Game Over", JOptionPane.ERROR_MESSAGE, null);
| + | |
− | for (Component c : pnlMain.getComponents()) {
| + | |
− | GameButton b = (GameButton) c;
| + | |
− | b.setEnabled(false);
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | private boolean isBomb() {
| + | |
− | Random r = new Random();
| + | |
− | return r.nextInt(ROWS) == 1;
| + | |
− | }
| + | |
− | | + | |
− | public static void main(String[] args) {
| + | |
− | JFrame fr = new JFrame("MineSweeper");
| + | |
− | fr.setLayout(new BorderLayout());
| + | |
− | fr.add(new MineSweeper());
| + | |
− | fr.setResizable(false);
| + | |
− | fr.setSize(250, 350);
| + | |
− | fr.setLocationRelativeTo(null);
| + | |
− | fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
| + | |
− | fr.setVisible(true);
| + | |
− | }
| + | |
− | | + | |
− | class GameButton extends JButton {
| + | |
− | private boolean isBomb = false;
| + | |
− | private Point position = null;
| + | |
− | private int bombCount = 0;
| + | |
− | private State state = State.Initial;
| + | |
− | | + | |
− | public void setState(State state) {
| + | |
− | this.state = state;
| + | |
− | if (getBombCount() == 0 && !isBomb) {
| + | |
− | setEnabled(false);
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | public State getState() {
| + | |
− | return state;
| + | |
− | }
| + | |
− | | + | |
− | public int getBombCount() {
| + | |
− | return bombCount;
| + | |
− | }
| + | |
− | | + | |
− | public void setBombCount(int bombCount) {
| + | |
− | this.bombCount = bombCount;
| + | |
− | }
| + | |
− | | + | |
− | public GameButton(Point position) {
| + | |
− | setPosition(position);
| + | |
− | setText(position.toString());
| + | |
− | }
| + | |
− | | + | |
− | public Point getPosition() {
| + | |
− | return position;
| + | |
− | }
| + | |
− | | + | |
− | public void setPosition(Point position) {
| + | |
− | this.position = position;
| + | |
− | }
| + | |
− | | + | |
− | public boolean isBomb() {
| + | |
− | return isBomb;
| + | |
− | }
| + | |
− | | + | |
− | public void setBomb(boolean isBomb) {
| + | |
− | this.isBomb = isBomb;
| + | |
− | }
| + | |
− | | + | |
− | @Override
| + | |
− | public String getText() {
| + | |
− | if (state == State.Initial) {
| + | |
− | return "";
| + | |
− | }
| + | |
− | if (state == State.Marked) {
| + | |
− | return "\u00B6";
| + | |
− | }
| + | |
− | if (state == State.Clicked) {
| + | |
− | if (isBomb) {
| + | |
− | return "<html><font size='16'><b>*</b></font></html>";
| + | |
− | } else {
| + | |
− | if (getBombCount() > 0)
| + | |
− | return getBombCount() + "";
| + | |
− | else
| + | |
− | return "";
| + | |
− | }
| + | |
− | }
| + | |
− | if (state == State.WrongMarked) {
| + | |
− | return "X";
| + | |
− | }
| + | |
− | return super.getText();
| + | |
− | }
| + | |
− | | + | |
− | @Override
| + | |
− | public Color getBackground() {
| + | |
− | if (isColorCheatOn && isBomb) {
| + | |
− | return Color.MAGENTA;
| + | |
− | }
| + | |
− | if (state == State.Clicked) {
| + | |
− | if (isBomb) {
| + | |
− | return Color.RED;
| + | |
− | }
| + | |
− | if (getBombCount() > 0) {
| + | |
− | return Color.GREEN;
| + | |
− | }
| + | |
− | }
| + | |
− | if (isEnabled()) {
| + | |
− | return Color.YELLOW.brighter();
| + | |
− | } else {
| + | |
− | return super.getBackground();
| + | |
− | }
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | private Point[] getSurroundings(Point cPoint) {
| + | |
− | int cX = (int) cPoint.getX();
| + | |
− | int cY = (int) cPoint.getY();
| + | |
− | Point[] points = { new Point(cX - 1, cY - 1), new Point(cX - 1, cY), new Point(cX - 1, cY + 1), new Point(cX, cY - 1), new Point(cX, cY + 1),
| + | |
− | new Point(cX + 1, cY - 1), new Point(cX + 1, cY), new Point(cX + 1, cY + 1) };
| + | |
− | return points;
| + | |
− | }
| + | |
− | | + | |
− | private void updateBombCount(GameButton btn, Component[] components) {
| + | |
− | Point[] points = getSurroundings(btn.getPosition());
| + | |
− | | + | |
− | for (Point p : points) {
| + | |
− | GameButton b = getButtonAt(components, p);
| + | |
− | if (b != null && b.isBomb()) {
| + | |
− | btn.setBombCount(btn.getBombCount() + 1);
| + | |
− | }
| + | |
− | }
| + | |
− | btn.setText(btn.getBombCount() + "");
| + | |
− | }
| + | |
− | | + | |
− | private GameButton getButtonAt(Component[] components, Point position) {
| + | |
− | for (Component btn : components) {
| + | |
− | if ((((GameButton) btn).getPosition().equals(position))) {
| + | |
− | return (GameButton) btn;
| + | |
− | }
| + | |
− | }
| + | |
− | return null;
| + | |
− | }
| + | |
− | | + | |
− | public void eventDispatched(AWTEvent event) {
| + | |
− | if (KeyEvent.class.isInstance(event) && ((KeyEvent) (event)).getID() == KeyEvent.KEY_RELEASED) {
| + | |
− | if (((KeyEvent) (event)).getKeyCode() == KeyEvent.VK_F1) {
| + | |
− | showAbout();
| + | |
− | }
| + | |
− | if (((KeyEvent) (event)).getKeyCode() == KeyEvent.VK_F2) {
| + | |
− | restartGame();
| + | |
− | }
| + | |
− | if (((KeyEvent) (event)).getKeyCode() == KeyEvent.VK_F3) {
| + | |
− | isColorCheatOn = !isColorCheatOn;
| + | |
− | if (state == GameState.Playing) {
| + | |
− | pnlMain.updateUI();
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | if (((KeyEvent) (event)).getKeyCode() == KeyEvent.VK_F12) {
| + | |
− | for (Component c : pnlMain.getComponents()) {
| + | |
− | GameButton b = (GameButton) c;
| + | |
− | if (b.isBomb() == false) {
| + | |
− | b.setState(State.Clicked);
| + | |
− | } else {
| + | |
− | b.setState(State.Marked);
| + | |
− | }
| + | |
− | b.setEnabled(false);
| + | |
− | }
| + | |
− | checkGameState();
| + | |
− | }
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | public void actionPerformed(ActionEvent actionEvent) {
| + | |
− | if (actionEvent.getSource() == btnReset) {
| + | |
− | restartGame();
| + | |
− | }
| + | |
− | }
| + | |
− | }</pre>
| + | |
− | | + | |
− | Go back to [[HelloWorld in Java]] and follow the instructions for Exporting and Running a Java Program on the Over COM. When you are done, you should be able to play a 9x10 colorful game of MineSweeper on the monitor connected to your Overo COM expansion board.
| + | |
What do you think of those vibrating massage chairs at the malls or the walk-in massage places?
It's a good quick fix to get a knot worked out or problem area that's stretched. You can get pretty kfrldbet relaxed in the course of 15 minutes. I've done it myself - even gone into Brookstone just to sit in the massage chairs.
You get massages yourself, then?
Absolutely. I trade with colleagues for their services. I'm very fussy - I look for someone who has an attunement to the amount of pressure, speed of strokes, and a certain quality of touch.
How do you convince someone who doesn't believe in body work?
I don't spend a lot of time trying to sway people. Either you're ready for massage in shanghai therapy or you're not. But it's definitely a feel-good option for many.
kfrldbet